<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발공방</title>
    <link>https://kakaoncw.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 1 Jun 2026 17:31:02 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>남찬우</managingEditor>
    <item>
      <title>언리얼 엔진 5에서 대난투 스타일 게임을 위한 카메라 시스템 확장하기</title>
      <link>https://kakaoncw.tistory.com/101</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;소개 및 배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대난투 스타일의 사이드 뷰 멀티플레이어 게임을 언리얼 엔진 5로 개발하는 과정에서 가장 중요한 요소 중 하나는 카메라 시스템입니다. 특히 여러 플레이어가 동시에 화면에 표시되어야 하는 대난투 게임의 특성상, 기존 언리얼 엔진의 카메라 컴포넌트만으로는 한계가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 언리얼 엔진의 FollowCameraComponent는 단일 플레이어를 추적하는 용도로 설계되어 있어, 여러 플레이어와 보스 몬스터를 동시에 화면에 담아내거나, 특정 상황에 맞게 카메라 모드를 전환하는 기능이 부족했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 한계를 극복하기 위해 기존 컴포넌트를 확장하여 다음 기능들을 구현했습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 플레이어와 보스를 동시에 화면에 표시하는 그룹 카메라 모드&lt;/li&gt;
&lt;li&gt;개인 카메라와 그룹 카메라 간 부드러운 전환&lt;/li&gt;
&lt;li&gt;멀티플레이어 환경에서의 네트워크 지원&lt;/li&gt;
&lt;li&gt;특정 영역에서 카메라 설정을 변경하는 확장된 트리거 시스템&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기반 클래스 분석&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FollowCameraComponent&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 FollowCameraComponent는 다음과 같은 기능을 제공합니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐릭터 자동 추적 (위치와 회전 모두)&lt;/li&gt;
&lt;li&gt;줌 거리, 리드 거리(캐릭터 진행 방향으로 카메라가 앞서가는 거리), 높이 등 설정&lt;/li&gt;
&lt;li&gt;부드러운 보간을 통한 자연스러운 카메라 움직임&lt;/li&gt;
&lt;li&gt;사이드 뷰 게임에 최적화된 카메라 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 컴포넌트는 기본적인 기능은 훌륭하지만, 다음과 같은 제한이 있었습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 타겟만 추적 가능&lt;/li&gt;
&lt;li&gt;멀티플레이어를 위한 네트워크 기능 부족&lt;/li&gt;
&lt;li&gt;다양한 카메라 모드나 전환 시스템 부재&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CameraTrigger&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CameraTrigger 클래스는 플레이어가 특정 영역에 진입하면 카메라 설정을 자동으로 변경해주는 기능을 제공합니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트리거 영역 진입/이탈 시 카메라 파라미터 변경&lt;/li&gt;
&lt;li&gt;이전 카메라 설정으로 복원하는 기능 (UndoAfterEndOverlap)&lt;/li&gt;
&lt;li&gt;부드러운 전환을 위한 블렌드 타임 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 트리거 시스템도 유용하지만, 확장된 카메라 컴포넌트와 호환되도록 보완이 필요했습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;카메라 컴포넌트 확장 - SmashCameraComponent&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클래스 구조 및 상속 관계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SmashCameraComponent는 기존 FollowCameraComponent를 상속받아 기본 기능을 유지하면서 새로운 기능을 추가했습니다:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;UCLASS()
class SMASHBRAWL_API USmashCameraComponent : public UFollowCameraComponent
{
    GENERATED_BODY()
    
    // 추가 속성 및 메서드...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 상속 구조를 활용하면 기존 코드를 재작성할 필요 없이 필요한 기능만 확장할 수 있어 개발 효율성이 높아집니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;카메라 모드 시스템&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 중요한 확장 기능 중 하나는 두 가지 카메라 모드를 지원하는 시스템입니다:&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// 카메라 모드 열거형
UENUM(BlueprintType)
enum class ECameraMode : uint8
{
    Group UMETA(DisplayName = &quot;Group&quot;), // 그룹 모드 (모든 플레이어 표시)
    Default UMETA(DisplayName = &quot;Default&quot;) // 기본 모드 (개인 플레이어 추적)
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Default 모드&lt;/b&gt;: 기존 카메라처럼 단일 플레이어를 추적하는 모드&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Group 모드&lt;/b&gt;: 모든 플레이어와 주요 적(보스)이 화면에 표시되도록 자동 조절하는 모드&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모드는 다음과 같이 설정할 수 있습니다:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void USmashCameraComponent::SetCameraMode(ECameraMode NewMode, float BlendTime)
{
    // 이전 모드 저장 (복원 기능을 위해)
    PreviousCameraMode = CurrentCameraMode;

    // 새 모드 설정
    CurrentCameraMode = NewMode;

    // 모드에 맞는 설정 적용
    ApplyCameraModeSettings(NewMode, BlendTime);
    
    // 카메라 위치 및 기타 설정 업데이트...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 모드에 맞는 설정을 자동으로 적용하여 사용자가 직접 모든 설정을 변경할 필요가 없도록 했습니다:&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;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;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다중 타겟 추적 시스템&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그룹 모드의 핵심은 여러 타겟을 동시에 추적하는 기능입니다. 이를 위해 타겟 관리 시스템을 구현했습니다:&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;// 타겟 관리 함수
void USmashCameraComponent::AddTargetActor(AActor* TargetActor)
{
    // 중복 방지 및 유효성 검사
    if (!TargetActor || TargetActors.Contains(TargetActor))
    {
        return;
    }

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

    // 첫 번째 타겟은 메인 타겟으로 설정
    if (!MainTarget)
    {
        MainTarget = TargetActor;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그룹 모드에서는 모든 타겟이 화면에 표시되도록 카메라 위치와 줌을 자동으로 조정합니다:&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;void USmashCameraComponent::UpdateGroupCamera(float DeltaTime)
{
    // 타겟이 하나거나 없으면 기본 모드처럼 처리
    if (TargetActors.Num() &amp;lt;= 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-&amp;gt;GetActorLocation();
            
            // 평균 위치 계산
            TargetCenterPos += TargetPos;
            ValidTargetCount++;
            
            // 경계 상자 업데이트
            MinBound.X = FMath::Min(MinBound.X, TargetPos.X);
            MaxBound.X = FMath::Max(MaxBound.X, TargetPos.X);
            // Y, Z 축도 동일하게 처리...
        }
    }
    
    // 평균 위치 계산
    if (ValidTargetCount &amp;gt; 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
        );
        
        // 카메라 위치와 줌 조정...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식으로 플레이어가 서로 멀어져도 모두 화면에 표시되며, 가까워지면 자연스럽게 줌인하여 상세한 액션을 볼 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네트워크 복제 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티플레이어 게임에서는 네트워크 복제가 중요합니다. 필요한 속성만 선택적으로 복제하여 성능을 최적화했습니다:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;void USmashCameraComponent::GetLifetimeReplicatedProps(TArray&amp;lt;FLifetimeProperty&amp;gt;&amp;amp; OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);

    // 복제할 속성들만 선택적으로 등록
    DOREPLIFETIME(USmashCameraComponent, RegisteredPlayers);
    DOREPLIFETIME(USmashCameraComponent, bAutoCameraEnabled);
    DOREPLIFETIME(USmashCameraComponent, bIsMasterCamera);
    // CurrentCameraMode는 의도적으로 복제하지 않음
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특별히 주목할 점은 CurrentCameraMode를 복제 대상에서 제외한 것입니다. 이는 각 클라이언트가 자신의 화면에서 원하는 카메라 모드를 독립적으로 설정할 수 있게 하기 위한 의도적인 설계 결정이었습니다. 예를 들어, 한 플레이어는 그룹 모드로 전체 상황을 보고, 다른 플레이어는 개인 모드로 자신의 캐릭터에 집중할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마스터 카메라 시스템&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 플레이어가 있는 환경에서 일관된 이벤트 처리를 위해 마스터 카메라 개념을 도입했습니다:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;void USmashCameraComponent::BecomesMasterCamera()
{
    // 권한 확인 (서버만 설정 가능)
    if (GetOwner()-&amp;gt;HasAuthority())
    {
        // 이미 마스터 카메라면 중복 처리 방지
        if (bIsMasterCamera)
        {
            return;
        }

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

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

        // 모든 클라이언트에게 알림
        Multicast_BecomesMasterCamera();
    }
    else
    {
        // 클라이언트는 서버에 요청
        Server_BecomesMasterCamera();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마스터 카메라는 보스 인트로 시퀀스, 중요 이벤트 카메라 효과 등 게임 전체적인 카메라 이벤트를 처리하는 역할을 담당합니다. 이로써 여러 플레이어가 있어도 일관된 시네마틱 경험을 제공할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;카메라 효과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 피드백을 강화하기 위한 다양한 카메라 효과도 구현했습니다. 대표적으로 카메라 흔들림 효과:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;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 &amp;gt;= 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;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 효과는 보스의 강력한 공격이나 중요한 이벤트 발생 시 몰입감을 높이는 데 사용됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;카메라 트리거 확장 - SmashCameraTrigger&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 CameraTrigger 클래스를 확장하여 SmashCameraComponent와 원활하게 연동되는 SmashCameraTrigger를 구현했습니다:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;UCLASS()
class SMASHBRAWL_API ASmashCameraTrigger : public ACameraTrigger
{
    GENERATED_BODY()
    
    // 커스텀 블렌드 시간 오버라이드
    UPROPERTY(EditAnywhere, Category = &quot;Camera|Preset&quot;)
    float CustomBlendTime = 0.0f;
    
    // 기타 속성 및 메서드...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;트리거 동작 메커니즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리거의 핵심 기능은 플레이어가 특정 영역에 진입하면 카메라 설정을 변경하고, 영역을 벗어나면 이전 설정으로 돌아가는 것입니다:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void ASmashCameraTrigger::OnOverlapBegin_Implementation(class AActor* ThisActor, class AActor* OtherActor)
{
    // 기본 트리거 기능 실행
    Super::OnOverlapBegin(ThisActor, OtherActor);
    
    // SmashCameraComponent 찾기
    USmashCameraComponent* CameraComp = GetSmashCameraComponent(OtherActor);
    if (CameraComp)
    {
        // 커스텀 블렌드 시간 또는 기본값 사용
        float BlendTime = CustomBlendTime &amp;gt; 0.0f ? 
            CustomBlendTime : CameraComp-&amp;gt;GetDefaultCameraBlendSpeed();
        
        // 현재 모드와 다를 때만 변경
        if (CameraComp-&amp;gt;GetCameraMode() != ECameraMode::Default)
        {
            CameraComp-&amp;gt;SetCameraMode(ECameraMode::Default, BlendTime);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식의 큰 장점은 부모 클래스인 CameraTrigger의 UndoAfterEndOverlap 속성을 그대로 활용할 수 있다는 것입니다. 트리거에 진입하기 전 상태를 저장했다가, 트리거 영역을 벗어날 때 자동으로 복원합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;카메라 컴포넌트 찾기 및 캐싱&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트리거가 작동할 때마다 매번 카메라 컴포넌트를 찾는 것은 비효율적이므로, 캐싱 메커니즘을 구현했습니다:&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;USmashCameraComponent* ASmashCameraTrigger::GetSmashCameraComponent(AActor* Actor)
{
    // 이미 캐싱된 참조가 있고 유효하면 반환
    if (SmashCameraComp &amp;amp;&amp;amp; SmashCameraComp-&amp;gt;GetOwner() == Actor)
    {
        return SmashCameraComp;
    }
    
    // SmashCharacter 먼저 확인
    if (ASmashCharacter* SmashChar = Cast&amp;lt;ASmashCharacter&amp;gt;(Actor))
    {
        SmashCameraComp = SmashChar-&amp;gt;SmashCameraComponent;
        return SmashCameraComp;
    }
    
    // 직접 컴포넌트 찾기
    if (USmashCameraComponent* FoundComp = Actor ? Actor-&amp;gt;FindComponentByClass&amp;lt;USmashCameraComponent&amp;gt;() : nullptr)
    {
        SmashCameraComp = FoundComp;
        return SmashCameraComp;
    }
    
    // 찾지 못한 경우
    return nullptr;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 같은 액터에 대해 중복 검색을 피하고 성능을 개선할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 도전과 해결책&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;다중 타겟 추적의 성능 최적화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그룹 카메라 모드에서 가장 큰 도전은 여러 타겟을 추적하면서도 성능을 유지하는 것이었습니다. 다음과 같은 최적화 전략을 적용했습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;조건부 계산&lt;/b&gt;: 타겟이 하나뿐이거나 없을 때는 그룹 모드 계산을 건너뜁니다.&lt;/li&gt;
&lt;li&gt;if (TargetActors.Num() &amp;lt;= 1) { UpdateDefaultCamera(DeltaTime); return; }&lt;/li&gt;
&lt;li&gt;&lt;b&gt;부드러운 보간&lt;/b&gt;: 매 프레임마다 설정을 급격히 변경하는 대신 보간을 통해 부드럽게 변경합니다.&lt;/li&gt;
&lt;li&gt;float NewZoomDistance = FMath::FInterpTo( CameraZoomDistance, OptimalZoom, DeltaTime, ZoomSmoothingFactor );&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유효성 검사&lt;/b&gt;: 모든 계산 전에 타겟의 유효성을 확인하여 불필요한 연산을 방지합니다.&lt;/li&gt;
&lt;li&gt;if (IsValid(Target)) { // 계산 수행 }&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네트워크 동기화와 클라이언트 독립성의 균형&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티플레이어 게임에서는 네트워크 동기화와 클라이언트 독립성 사이의 균형이 중요합니다. 다음과 같은 접근 방식을 취했습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;선택적 복제&lt;/b&gt;: 모든 데이터를 복제하는 대신, 정말 필요한 데이터만 복제합니다.&lt;/li&gt;
&lt;li&gt;DOREPLIFETIME(USmashCameraComponent, RegisteredPlayers); DOREPLIFETIME(USmashCameraComponent, bIsMasterCamera); // CurrentCameraMode는 복제하지 않음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 권한 위임&lt;/b&gt;: 사용자 경험과 직접 관련된 설정은 각 클라이언트가 제어하도록 합니다.&lt;/li&gt;
&lt;li&gt;// 각 클라이언트가 독립적으로 카메라 모드 선택 가능 void USmashCameraComponent::ToggleCameraMode() { if (CurrentCameraMode == ECameraMode::Group) SetCameraMode(ECameraMode::Default, 1.0f); else SetCameraMode(ECameraMode::Group, 1.0f); }&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 권한 존중&lt;/b&gt;: 보안이나 게임 진행에 중요한 결정은 서버가 담당합니다.&lt;/li&gt;
&lt;li&gt;// 마스터 카메라 설정은 서버만 가능 if (GetOwner()-&amp;gt;HasAuthority()) { // 마스터 카메라 설정 로직 } else { // 서버에 요청 Server_BecomesMasterCamera(); }&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 접근 방식으로 네트워크 부하를 줄이면서도 원활한 멀티플레이어 경험을 제공할 수 있었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배운 점 및 향후 개선 방향&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언리얼 엔진에서 컴포넌트 확장의 유연성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 작업을 통해 언리얼 엔진에서 기존 컴포넌트를 확장하는 방식의 유연성과 효율성을 배웠습니다. 처음부터 모든 것을 구현하는 대신 기존 기능을 활용하고 확장함으로써:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발 시간 단축&lt;/li&gt;
&lt;li&gt;기존 코드의 안정성 활용&lt;/li&gt;
&lt;li&gt;필요한 부분만 집중적으로 개선&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 접근 방식은 기능 확장뿐 아니라 코드 유지보수 측면에서도 큰 이점을 제공합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네트워크 복제를 위한 설계 원칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티플레이어 게임에서 네트워크 복제는 항상 어려운 과제입니다. 이번 작업에서 효과적이었던 설계 원칙은 다음과 같습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;최소 복제 원칙&lt;/b&gt;: 정말 필요한 데이터만 복제하여 네트워크 트래픽 최소화&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 권한 분배&lt;/b&gt;: 사용자 경험 관련 요소는 각 클라이언트가 제어하도록 위임&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명확한 RPC 체계&lt;/b&gt;: 서버-클라이언트 간 통신에 명시적인 RPC 함수 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 원칙을 적용하면 네트워크 성능을 최적화하면서도 좋은 사용자 경험을 제공할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능 최적화 가능성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 구현에서도 성능은 양호하지만, 대규모 멀티플레이어나 복잡한 환경에서는 추가 최적화가 필요할 수 있습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;계산 주기 조정&lt;/b&gt;: 매 프레임이 아닌 일정 간격으로 무거운 계산 수행&lt;/li&gt;
&lt;li&gt;// 예시: 0.1초마다 한 번만 계산 if (OptimalZoomTimer &amp;gt;= 0.1f) { CalculateOptimalZoomDistance(); OptimalZoomTimer = 0.0f; } OptimalZoomTimer += DeltaTime;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;중요도 기반 시스템&lt;/b&gt;: 화면 중앙에 가까운 타겟이나 중요 타겟에 가중치 부여&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LOD(Level of Detail) 시스템&lt;/b&gt;: 카메라 거리에 따라 업데이트 빈도나 정밀도 조정&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;향후 추가 기능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 구현을 기반으로 다음과 같은 기능을 추가로 개발할 계획입니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;보스 인트로 시퀀스&lt;/b&gt;: 보스 등장 시 특별한 카메라 시퀀스로 긴장감 조성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시네마틱 이벤트 트리거&lt;/b&gt;: 스토리 진행에 따른 자동 카메라 시퀀스&lt;/li&gt;
&lt;li&gt;&lt;b&gt;카메라 필터 효과&lt;/b&gt;: 게임 상황에 따른 시각 효과(블룸, 색상 그레이딩, 모션 블러 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;플레이어별 설정&lt;/b&gt;: 각 플레이어가 자신의 카메라 설정을 커스터마이징할 수 있는 옵션&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 기능을 추가하면 게임의 시각적 경험과 몰입도를 더욱 향상시킬 수 있을 것입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 개발을 통해 언리얼 엔진 5에서 대난투 스타일 게임에 적합한 확장된 카메라 시스템을 성공적으로 구현했습니다. FollowCameraComponent와 CameraTrigger의 기존 기능을 유지하면서, 멀티플레이어 지원, 그룹 카메라 모드, 확장된 트리거 시스템 등 필요한 기능을 추가했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 카메라 시스템은 다음과 같은 주요 이점을 제공합니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;유연한 카메라 모드&lt;/b&gt;: 개인 모드와 그룹 모드 간 자유로운 전환으로 게임 상황에 따른 최적의 시점 제공&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모든 플레이어 가시성 보장&lt;/b&gt;: 액션 게임에서 가장 중요한 요소인 모든 캐릭터의 위치와 상태를 항상 확인 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네트워크 최적화&lt;/b&gt;: 필요한 데이터만 복제하여 효율적인 네트워크 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장 가능한 구조&lt;/b&gt;: 추가 기능과 효과를 쉽게 구현할 수 있는 견고한 아키텍처&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카메라 시스템은 게임의 핵심 경험을 결정하는 중요한 요소입니다. 이번에 구현한 시스템은 대난투 스타일 게임의 빠른 액션과 다양한 상황에 유연하게 대응할 수 있는 견고한 기반을 제공할 것입니다. 향후 게임 개발이 진행됨에 따라 더 많은 기능과 최적화를 통해 이 시스템을 계속 발전시켜 나갈 예정입니다.&lt;/p&gt;</description>
      <category>UnrealCamp</category>
      <author>남찬우</author>
      <guid isPermaLink="true">https://kakaoncw.tistory.com/101</guid>
      <comments>https://kakaoncw.tistory.com/101#entry101comment</comments>
      <pubDate>Fri, 11 Apr 2025 21:31:04 +0900</pubDate>
    </item>
    <item>
      <title>프로젝트 기획서 [Smash Raid]</title>
      <link>https://kakaoncw.tistory.com/100</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;닌텐도의 &amp;lsquo;대난투&amp;rsquo; 스타일의 플랫폼 액션과 보스 레이드의 협동 요소를 결합한 멀티플레이 액션 게임.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장르&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플랫폼 액션, 멀티플레이 레이드&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;플레이 방식&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;온라인 멀티플레이(최대 4인)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 게임 특징 (Core Features)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플랫폼 액션 전투 기반의 PvE 보스 레이드&lt;/li&gt;
&lt;li&gt;직업 구분 없이 동일한 기본 캐릭터로 시작&lt;/li&gt;
&lt;li&gt;직관적인 조작과 간단한 UI&lt;/li&gt;
&lt;li&gt;협동 플레이와 명확한 팀워크 강조&lt;/li&gt;
&lt;li&gt;보스전의 페이즈 변화 및 다양한 기믹 패턴 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 게임 흐름 (Flow of Gameplay)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. 메인 화면&lt;/h3&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;Raid Mode
Battle Mode (미구현, 확장 가능)
Option (사운드 조절)
Exit Game
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. Raid Mode 진입 및 세션 관리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Raid Mode 선택 시, 기존 세션이 없으면 자동으로 세션 생성(호스트)&lt;/li&gt;
&lt;li&gt;세션 생성 후 다른 플레이어 참가 가능(최대 4인)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 레이드 시작 전 로비 UI&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;화면 구성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;좌측&lt;/b&gt;: 현재 접속한 플레이어 목록 표시&lt;/li&gt;
&lt;li&gt;&lt;b&gt;우측&lt;/b&gt;: 게임 설정 옵션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게임 시작 버튼(호스트 전용, 2인 이상 시 활성화)&lt;/li&gt;
&lt;li&gt;게임 종료 버튼&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 게임 시작 및 규칙&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-1. 게임 진행 규칙 (세부 규칙 보완)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플레이어 간 피해는 없음(아군 공격 불가능), 넉백만 존재함&lt;/li&gt;
&lt;li&gt;게임 목표는 &lt;b&gt;보스 몬스터 처치&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;캐릭터의 데미지, 스킬, 기본 조작 방식은 기존 시스템과 동일&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보스의 HP는 참여 인원에 비례해 증가&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(기본 HP) x (참여 플레이어 수) x (균형 조정값)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;보스 몬스터 처치 시 &lt;b&gt;게임 클리어&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;모든 플레이어가 동시에 사망할 경우, 즉시 &lt;b&gt;게임 오버&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게임 오버 시 재도전 또는 로비로 돌아가기 선택 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;보스는 페이즈가 넘어갈 때마다 맵이 변경됨(점프맵 변경)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-2. 카메라 시스템&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 플레이어 및 보스 몬스터가 동시에 보이도록 카메라가 실시간으로 이동하여 화면 공유&lt;/li&gt;
&lt;li&gt;플레이어가 화면 밖으로 밀려나지 않도록 제한적 카메라 이동 제약 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 인게임 HUD&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;상단&lt;/b&gt;: 보스 체력 HUD&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하단&lt;/b&gt;: 플레이어별 피격 % HUD (대난투와 동일한 시스템)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 부활 시스템 (상세 규칙 추가)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플레이어 사망 시 일정 시간(약 5초) 후, 맵 내 랜덤 플랫폼 위에 &lt;b&gt;시체로 등장&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;다른 플레이어가 사망자의 시체에 약 3초간 오버랩 시 해당 플레이어 부활&lt;/li&gt;
&lt;li&gt;부활 시 피격%는 50%로 시작(기본 패널티 적용)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. Debuff 시스템 (상세 규칙 추가)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플레이어 피격 시 일정 확률로 Debuff 발생&lt;/li&gt;
&lt;li&gt;피격%가 높을수록 Debuff에 걸릴 확률 및 지속시간 증가&lt;/li&gt;
&lt;li&gt;각 Debuff는 독립적으로 작용하며, 중복 가능&lt;/li&gt;
&lt;li&gt;동일 Debuff 중첩 시 지속시간 초기화&lt;/li&gt;
&lt;li&gt;Debuff는 일정 시간이 지나면 자연 소멸됨(약 10초 지속)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Debuff 종류&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름 효과 시각 효과&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;td&gt;이동속도 및 점프력 감소(50%)&lt;/td&gt;
&lt;td&gt;캐릭터 주위 푸른색 기운&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blind&lt;/td&gt;
&lt;td&gt;플레이어 시야 좁아짐(자기 캐릭터 주변 제외 전체 화면 암전)&lt;/td&gt;
&lt;td&gt;캐릭터 머리 위 검은 연기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 몬스터 및 아이템 (세부 규칙 추가)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;소형 몬스터&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게임 진행 중 랜덤한 주기로 맵 상단에서 등장&lt;/li&gt;
&lt;li&gt;체력 적고 UI 없음(빠르게 처치 가능)&lt;/li&gt;
&lt;li&gt;처치 시 항상 회복 포션 드롭&lt;/li&gt;
&lt;li&gt;포션 획득 시 플레이어 피격% 20% 감소&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;포션 획득 규칙&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플레이어와 아이템 오버랩 시 즉시 소모됨&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 보스 몬스터 (용 형태)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보스는 플랫폼 뒤 배경에 위치(좌측 손, 우측 손, 머리 공격 가능)&lt;/li&gt;
&lt;li&gt;머리 공격 시 추가 데미지(1.5배)&lt;/li&gt;
&lt;li&gt;보스 페이즈 변화 시 맵 변경 후 다음 페이즈 돌입&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;보스 패턴 상세 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패턴 이름 설명 및 대응 방법&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;물기&lt;/td&gt;
&lt;td&gt;보스가 크게 입을 벌리면 플레이어에게 중력 적용되어 입으로 끌려감. 닿으면 속박됨. 속박된 플레이어는 타 플레이어가 때려서 구출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;확정 속박&lt;/td&gt;
&lt;td&gt;특정 플레이어 지정하여 속박, 다른 플레이어가 맵 위쪽에 생성된 제단 파괴 시 해제. 제단은 방어 몬스터 등장하여 공격 방해&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;오버로드&lt;/td&gt;
&lt;td&gt;일정시간 특정 플레이어가 표적이 되며, 표적 플레이어가 피격 시 주변에 폭발 피해 발생. 일정 시간 피격 회피 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;카운터&lt;/td&gt;
&lt;td&gt;보스의 특정 공격 타이밍에 맞춰 지정된 키 입력 시 공격 무효화 및 역습 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;11. 게임 플레이 순서 (상세한 메커니즘)&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;① 게임 시작 및 플레이어 등장&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Raid Mode 진입 후 플레이어 2~4인이 로비에서 준비 완료 상태로 전환하면 게임이 시작됩니다.&lt;/li&gt;
&lt;li&gt;게임 시작 시 모든 플레이어는 맵 중앙 플랫폼 위에서 동시에 등장합니다.&lt;/li&gt;
&lt;li&gt;등장 직후 &lt;b&gt;3초의 무적 시간&lt;/b&gt;이 존재하며, 이 시간 동안은 피격과 Debuff에 걸리지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;② 캐릭터 기본 조작 및 이동 메커니즘&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플레이어는 좌우 이동, 점프(최대 &lt;b&gt;2단 점프&lt;/b&gt; 가능), 공격(기본 공격 및 특수 공격)을 수행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이동 키&lt;/b&gt;: 방향키 &amp;larr; &amp;rarr; (또는 WASD)로 좌우로 이동합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;점프 키&lt;/b&gt;: 점프 버튼(스페이스바 또는 패드 A/X 버튼)을 눌러 점프, 공중에서 한 번 더 눌러 2단 점프가 가능합니다.&lt;/li&gt;
&lt;li&gt;플랫폼 아래로 빠르게 내려가려면 방향키 아래 + 점프키를 동시에 입력합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;③ 기본 공격과 특수 공격&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플레이어는 기본 공격과 특수 공격으로 보스 및 소형 몬스터에게 데미지를 줄 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기본 공격&lt;/b&gt;(일반 타격):
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버튼(X 또는 마우스 좌클릭)을 연속적으로 입력해 최대 3타 콤보 공격을 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;보스의 팔 또는 머리에 명중하면 해당 부위의 HP가 감소합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특수 공격&lt;/b&gt;(스킬 사용):
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;버튼(Y 또는 마우스 우클릭)으로 사용하며, 쿨타임은 5초입니다.&lt;/li&gt;
&lt;li&gt;특수 공격은 기본 공격의 2배 피해를 주며, 보스의 머리에 적중하면 추가 피해(기본 데미지 &amp;times; 1.5)가 적용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;④ 보스에게 데미지를 주는 방식&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보스는 화면 뒤 배경에 크게 자리 잡고 있으며, 플랫폼에 닿는 신체 부위(왼손, 오른손, 머리)를 공격해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;머리&lt;/b&gt;에 대한 공격은 다른 부위보다 &lt;b&gt;1.5배 더 높은 데미지&lt;/b&gt;를 입힙니다.&lt;/li&gt;
&lt;li&gt;부위별 HP가 따로 존재하지 않고, 보스의 전체 HP만 존재하며 어떤 부위를 공격하더라도 보스 HP가 감소합니다.&lt;/li&gt;
&lt;li&gt;공격 시 보스는 잠시 경직 애니메이션을 재생하여 공격이 명중했음을 명확히 전달합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;⑤ 플레이어 피격 및 Debuff 메커니즘&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플레이어는 보스 또는 소형 몬스터에게 공격당하면 **피격%**가 누적됩니다.&lt;/li&gt;
&lt;li&gt;피격%가 높을수록 Debuff(슬로우, 블라인드)에 걸릴 확률 및 지속 시간이 증가합니다.&lt;/li&gt;
&lt;li&gt;Debuff에 걸리면 시각적으로 플레이어 주위에 특정 이펙트가 표시됩니다.&lt;/li&gt;
&lt;li&gt;피격%가 150%를 초과하면 보스의 공격에 매우 큰 넉백이 발생하여 맵 밖으로 떨어질 가능성이 높아집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;⑥ 소형 몬스터 및 아이템 드랍 메커니즘&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일정 간격(약 30초 간격)으로 상단 플랫폼 위쪽에서 소형 몬스터가 떨어집니다.&lt;/li&gt;
&lt;li&gt;소형 몬스터는 간단한 AI로 플레이어를 따라다니며 공격하지만, 체력이 낮아 쉽게 처치할 수 있습니다.&lt;/li&gt;
&lt;li&gt;소형 몬스터를 처치하면 반드시 &lt;b&gt;회복 포션&lt;/b&gt;이 드랍됩니다.&lt;/li&gt;
&lt;li&gt;회복 포션 획득 시 플레이어의 피격%가 즉시 &lt;b&gt;20% 감소&lt;/b&gt;합니다. (최소 0%까지 감소 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;⑦ 플레이어 사망 및 부활 메커니즘&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;피격 후 플랫폼 밖으로 떨어지면 플레이어는 사망 처리됩니다.&lt;/li&gt;
&lt;li&gt;사망 시 화면 상단에 &lt;b&gt;「부활까지 남은 시간: 5초」&lt;/b&gt; 카운트다운이 표시됩니다.&lt;/li&gt;
&lt;li&gt;5초 후 플레이어의 시체는 맵의 랜덤 플랫폼 위에 생성됩니다.&lt;/li&gt;
&lt;li&gt;다른 플레이어가 &lt;b&gt;약 3초간 시체 위에 머물러&lt;/b&gt; 오버랩하면 사망한 플레이어가 부활합니다.&lt;/li&gt;
&lt;li&gt;부활 직후 플레이어는 &lt;b&gt;약 3초의 무적시간&lt;/b&gt;과 함께 &lt;b&gt;50% 피격% 상태&lt;/b&gt;로 시작합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;⑧ 보스 패턴 대응 상세 메커니즘&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패턴명 구체적 메커니즘&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;물기&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;보스가 입을 벌리면 화면 중앙 방향으로 플레이어들이 끌려갑니다. 끌려갈 때는 이동 키 반대 방향으로 저항 가능하며, 빨려 들어가 보스의 입과 오버랩되면 &lt;b&gt;속박&lt;/b&gt; 상태가 됩니다. 속박 상태에서는 행동 불가하며 다른 플레이어가 공격으로 속박을 풀어줘야 합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;확정 속박(제단 파괴)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;특정 플레이어 1명이 랜덤으로 속박되며, 다른 플레이어들은 맵 위쪽에서 나타나는 제단을 찾아 파괴해야 합니다. 제단은 높은 곳에 위치하며, 2단 점프를 이용하여 접근할 수 있습니다. 제단 파괴 전까지 속박 플레이어는 행동 불가 상태입니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;오버로드(폭파)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;오버로드 대상 플레이어가 적색으로 표시되며, 일정 시간(5초) 동안 공격을 피해야 합니다. 이 시간에 맞아버리면 주변 플레이어에게 추가 넉백 및 데미지를 줍니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;카운터(반격)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;특정 보스 공격 직전 화면에 &amp;lsquo;카운터!&amp;rsquo; 메시지가 뜨면, 지정된 버튼(기본값: Q 또는 컨트롤러 B)을 정확한 타이밍에 눌러 공격을 무효화하고 보스에게 역습을 가할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;⑨ 페이즈 변경 및 점프맵 메커니즘&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보스 체력이 70% / 40% 이하로 내려가면 각각 새로운 페이즈로 이동하며, 맵이 자동으로 변경됩니다.&lt;/li&gt;
&lt;li&gt;맵이 변경될 때 플레이어 캐릭터는 새로운 맵의 시작 위치로 자동 이동됩니다.&lt;/li&gt;
&lt;li&gt;페이즈가 바뀌면 보스는 새로운 공격 패턴을 추가하고 공격 속도 및 빈도가 증가합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;⑩ 게임 종료(승리 및 패배 조건)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보스 HP가 &lt;b&gt;0이 되면 승리&lt;/b&gt;이며, 클리어 연출과 함께 결과창이 표시됩니다.&lt;/li&gt;
&lt;li&gt;동시에 모든 플레이어가 사망하여 부활 대기 상태에 진입하면 &lt;b&gt;즉시 게임 오버&lt;/b&gt; 처리됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>UnrealCamp</category>
      <author>남찬우</author>
      <guid isPermaLink="true">https://kakaoncw.tistory.com/100</guid>
      <comments>https://kakaoncw.tistory.com/100#entry100comment</comments>
      <pubDate>Wed, 2 Apr 2025 20:25:11 +0900</pubDate>
    </item>
    <item>
      <title>언리얼 엔진 네트워크의 Role(롤)</title>
      <link>https://kakaoncw.tistory.com/99</link>
      <description>&lt;h1&gt;언리얼 엔진 네트워크의 Role(롤) 개념 자세히 설명&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진의 멀티플레이 네트워크 시스템에서 **Role(롤)**과 **Remote Role(리모트 롤)**은 매우 중요한 개념입니다. Actor가 어떤 기기(서버 또는 클라이언트)에 의해 **권한(Authority)**을 가지며 상태 변경이나 RPC(Remote Procedure Call)를 수행할 수 있는지를 결정하는 정보가 바로 Role과 Remote Role입니다 (&lt;a href=&quot;https://velog.io/@dltmdrl1244/UE5-Actor-Role-Remote-Role#:~:text=%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC%20%EA%B2%8C%EC%9E%84%ED%94%8C%EB%A0%88%EC%9D%B4%EC%97%90%EC%84%9C%20%EC%95%A1%ED%84%B0%EC%9D%98%20,%EB%A5%BC%20%EA%B2%B0%EC%A0%95%ED%95%9C%EB%8B%A4&quot;&gt;[UE5] Actor Role, Remote Role&lt;/a&gt;). 이 두 프로퍼티를 통해 다음과 같은 질문에 답할 수 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 액터가 네트워크 상 **복제(replication)**되고 있는가?&lt;/li&gt;
&lt;li&gt;액터에 대한 **권한(authority)**은 누구에게 있는가 (서버인지, 특정 클라이언트인지)?&lt;/li&gt;
&lt;li&gt;액터의 &lt;b&gt;복제 방식&lt;/b&gt;(자율적 업데이트인지, 시뮬레이션인지)은 무엇인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에서는 **Local Role(로컬 롤)**과 **Remote Role(리모트 롤)**의 차이, 각 Role 값의 정의와 역할, 액터의 네트워크 상태에 따른 Role 할당 방식, 서버와 클라이언트 간 권한 및 복제 처리, 그리고 코드에서 Role을 활용하는 방법을 구조화하여 설명합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Role vs. Remote Role: 로컬 롤과 리모트 롤의 차이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진에서 &lt;b&gt;Role&lt;/b&gt;은 해당 액터의 &lt;b&gt;로컬&lt;/b&gt;(현재 실행 중인 &lt;b&gt;로컬 머신&lt;/b&gt;)에서의 역할을 나타내고, &lt;b&gt;Remote Role&lt;/b&gt;은 &lt;b&gt;원격&lt;/b&gt;(네트워크 반대편 &lt;b&gt;원격 머신&lt;/b&gt;)에서의 역할을 나타냅니다 (&lt;a href=&quot;https://beyondunrealwiki.github.io/pages/role.html#:~:text=Role%20is%20a%20concept%20in,viewed%20from%20the%20local%20machine&quot;&gt;UnrealWiki: Role&lt;/a&gt;). 간단히 말해, &lt;b&gt;Local Role&lt;/b&gt;은 &amp;ldquo;내가 보는 이 액터의 역할&amp;rdquo;이고 &lt;b&gt;Remote Role&lt;/b&gt;은 &amp;ldquo;다른 피어(peer)에서 이 액터가 가지고 있는 역할&amp;rdquo;입니다 (&lt;a href=&quot;https://beyondunrealwiki.github.io/pages/role.html#:~:text=Role%20is%20a%20concept%20in,viewed%20from%20the%20local%20machine&quot;&gt;UnrealWiki: Role&lt;/a&gt;). 액터가 네트워크를 통해 다른 컴퓨터로 복제되면, &lt;b&gt;서버에 있던 Role 값이 클라이언트의 Remote Role로 전달되고, 서버에 설정된 Remote Role 값이 클라이언트의 Role로 설정&lt;/b&gt;되는 식으로 **두 값이 교차(swap)**됩니다 (&lt;a href=&quot;https://beyondunrealwiki.github.io/pages/role.html#:~:text=When%20an%20actor%20is%20replicated,viewed%20from%20the%20local%20machine&quot;&gt;UnrealWiki: Role&lt;/a&gt;). 즉, 동일한 액터도 &lt;b&gt;서버에서 바라본 Role/RemoteRole과 클라이언트에서 바라본 Role/RemoteRole이 서로 반대&lt;/b&gt;일 수 있습니다 (&lt;a href=&quot;https://coder-nwtma.tistory.com/23#:~:text=Role%EA%B3%BC%20RemoteRole%EC%9D%80%20%EB%88%84%EA%B0%80%20%EC%A1%B0%EC%82%AC%ED%95%98%EB%8A%94%EA%B0%80%EC%97%90%20%EB%94%B0%EB%9D%BC,%EA%B0%92%EC%9D%B4%20%EB%B0%98%EB%8C%80%EA%B0%80%20%EB%90%A0%20%EC%88%98%20%EC%9E%88%EB%8B%A4&quot;&gt;[UE4] Role, Remote Role&lt;/a&gt;).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 서버에서는 어떤 액터의 Role = ROLE_Authority이고 RemoteRole = ROLE_SimulatedProxy로 표시될 경우, 그 액터가 한 클라이언트에 복제되었을 때 &lt;b&gt;클라이언트 측에서는&lt;/b&gt; Role = ROLE_SimulatedProxy이고 RemoteRole = ROLE_Authority로 보이게 됩니다 (&lt;a href=&quot;https://coder-nwtma.tistory.com/23#:~:text=Role%EA%B3%BC%20RemoteRole%EC%9D%80%20%EB%88%84%EA%B0%80%20%EC%A1%B0%EC%82%AC%ED%95%98%EB%8A%94%EA%B0%80%EC%97%90%20%EB%94%B0%EB%9D%BC,%EA%B0%92%EC%9D%B4%20%EB%B0%98%EB%8C%80%EA%B0%80%20%EB%90%A0%20%EC%88%98%20%EC%9E%88%EB%8B%A4&quot;&gt;[UE4] Role, Remote Role&lt;/a&gt;). 이처럼 Role과 Remote Role은 &lt;b&gt;어느 측에서 정보를 조회하느냐에 따라&lt;/b&gt; 달라지며, 한 쪽의 Role이 다른 쪽의 RemoteRole로 대응되는 관계입니다. 정리하면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Local Role(로컬 Role)&lt;/b&gt;: 현재 이 액터를 소유한 로컬 인스턴스(서버 또는 클라이언트)에서의 역할 값입니다. 예를 들어 서버에서 보면 서버 자신의 Role이 로컬 롤이고, 클라이언트에서 보면 그 클라이언트에서의 Role이 로컬 롤입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Remote Role(원격 Role)&lt;/b&gt;: 반대편 원격 인스턴스(상대방)의 관점에서 이 액터가 어떤 역할인지를 나타냅니다. 클라이언트에서 액터의 RemoteRole이 &amp;ldquo;Authority&amp;rdquo;라면 그 액터의 원본은 서버에서 Authority를 가짐을 의미하고, 서버에서 액터의 RemoteRole이 &amp;ldquo;AutonomousProxy&amp;rdquo;라면 해당 액터를 어떤 클라이언트가 자율적으로 제어하고 있음을 의미합니다 (&lt;a href=&quot;https://iiii4.tistory.com/146#:~:text=%2A%20,%EA%B0%9D%EC%B2%B4%EA%B0%80%20%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C%20%EC%A0%9C%EC%96%B4%EB%90%98%EC%A7%80%20%EC%95%8A%EA%B3%A0%20%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%98%EB%90%A9%EB%8B%88%EB%8B%A4&quot;&gt;[UE5] Network Role&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요약하면, Role은 &lt;b&gt;자신이 속한 환경에서의 액터의 권한 상태&lt;/b&gt;를 나타내고, Remote Role은 &lt;b&gt;네트워크 반대편에서의 액터 권한 상태&lt;/b&gt;를 나타냅니다 (&lt;a href=&quot;https://iiii4.tistory.com/146#:~:text=%2A%20,%EA%B0%9D%EC%B2%B4%EA%B0%80%20%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C%20%EC%A0%9C%EC%96%B4%EB%90%98%EC%A7%80%20%EC%95%8A%EA%B3%A0%20%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%98%EB%90%A9%EB%8B%88%EB%8B%A4&quot;&gt;[UE5] Network Role&lt;/a&gt;). 이를 통해 코드에서 현재 실행 중인 환경이 서버인지 클라이언트인지, 액터가 누구에 의해 제어되고 있는지 등을 파악할 수 있습니다. (Role == ROLE_Authority이면 해당 코드가 &lt;b&gt;서버 권한&lt;/b&gt;으로 실행되고 있다는 의미가 됩니다.)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ENetRole 열거형과 각 Role의 정의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진에서는 Actor의 Role과 RemoteRole 프로퍼티가 내부적으로 ENetRole이라는 열거형(enum)으로 정의되어 있습니다 (&lt;a href=&quot;https://velog.io/@dltmdrl1244/UE5-Actor-Role-Remote-Role#:~:text=Actor%20Role%20States&quot;&gt;[UE5] Actor Role, Remote Role&lt;/a&gt;). 주요 Role 값과 그 의미는 다음과 같습니다. (각 Role 간의 차이를 명확히 비교하기 위해 표로 정리하였습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Role (ENetRole) 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;ROLE_Authority&lt;/b&gt; (권한)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;서버 권한을 가진 액터&lt;/b&gt;. 해당 액터는 서버에서 완전한 제어권을 가지며, 프로퍼티 변경을 추적하고 다른 클라이언트들에게 그 변경 사항을 **복제(replication)**하는 역할까지 수행합니다 (&lt;a href=&quot;https://velog.io/@dltmdrl1244/UE5-Actor-Role-Remote-Role#:~:text=&quot;&gt;[UE5] Actor Role, Remote Role&lt;/a&gt;). 서버의 Actor는 일반적으로 ROLE_Authority이며, 이 상태에서는 RPC(멀티캐스트 등)를 송신할 수 있습니다. 한편 클라이언트에서 ROLE_Authority인 액터는 &lt;b&gt;그 클라이언트에만 존재하는 로컬 액터&lt;/b&gt;로, 네트워크 복제를 하지 않는 경우에 나타납니다 (예: 이 액터는 해당 클라이언트에서만 존재하고 서버에는 존재하지 않음).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;ROLE_AutonomousProxy&lt;/b&gt; (자율 프록시)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;자율 프록시 액터&lt;/b&gt;. 특정 &lt;b&gt;클라이언트가 소유 및 직접 제어&lt;/b&gt;하는 액터입니다. 해당 클라이언트에서는 이 액터에 대해 &lt;b&gt;예측(Prediction)&lt;/b&gt; 로직이나 사용자 입력 처리가 가능하며, 필요에 따라 서버로 RPC(예: Server 함수 호출)를 보낼 수 있습니다 (&lt;a href=&quot;https://velog.io/@dltmdrl1244/UE5-Actor-Role-Remote-Role#:~:text=&quot;&gt;[UE5] Actor Role, Remote Role&lt;/a&gt;). 주로 플레이어가 조종하는 Pawn이나 Character가 이에 해당하며, 서버에서는 이러한 액터를 해당 소유 클라이언트에 대해 &lt;b&gt;AutonomousProxy로 지정&lt;/b&gt;합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;ROLE_SimulatedProxy&lt;/b&gt; (시뮬레이션 프록시)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;시뮬레이션 프록시 액터&lt;/b&gt;. 이 액터는 **원격 시스템(서버 또는 다른 클라이언트)**에서 제어하며, 로컬 시스템에서는 해당 액터를 &lt;b&gt;복제된 데이터로 시뮬레이션&lt;/b&gt;만 합니다 (&lt;a href=&quot;https://velog.io/@dltmdrl1244/UE5-Actor-Role-Remote-Role#:~:text=&quot;&gt;[UE5] Actor Role, Remote Role&lt;/a&gt;). 다시 말해, 로컬에서는 자체적으로 상태를 변경하거나 서버로 RPC를 보낼 수 없고, 서버로부터 전송된 위치/상태 업데이트를 적용하여 모습만 동기화합니다 (&lt;a href=&quot;https://velog.io/@dltmdrl1244/UE5-Actor-Role-Remote-Role#:~:text=&quot;&gt;[UE5] Actor Role, Remote Role&lt;/a&gt;). 플레이어가 직접 조종하지 않는 다른 플레이어의 캐릭터나 NPC 등이 클라이언트에서 SimulatedProxy로 동작하며, &lt;b&gt;수동적인(replicated) 객체&lt;/b&gt;로 취급됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;ROLE_None&lt;/b&gt; (없음)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;역할 없음&lt;/b&gt;. 이 액터는 &lt;b&gt;네트워크 복제되지 않는 경우&lt;/b&gt;에 해당합니다 (&lt;a href=&quot;https://velog.io/@dltmdrl1244/UE5-Actor-Role-Remote-Role#:~:text=&quot;&gt;[UE5] Actor Role, Remote Role&lt;/a&gt;). Role이나 RemoteRole이 None이면 엔진이 해당 액터의 상태를 다른 피어에 전송하지 않으며, 이 액터는 &lt;b&gt;현재 로컬 인스턴스에만 존재&lt;/b&gt;하게 됩니다. (예: 이펙트 전용 액터를 클라이언트에서만 생성한 경우, 클라이언트에서 Role=Authority이지만 RemoteRole=None으로 설정되어 다른 곳에 복제되지 않습니다.)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 표에서 볼 수 있듯이, **Authority(권한)**는 주로 &lt;b&gt;서버가 가지는 역할&lt;/b&gt;이며 액터의 &lt;b&gt;원본(primary) 인스턴스&lt;/b&gt;에 해당합니다. **AutonomousProxy(자율 프록시)**와 **SimulatedProxy(시뮬레이티드 프록시)**는 &lt;b&gt;클라이언트 측 복제본&lt;/b&gt;에서 나타나는 역할로, 해당 액터가 &lt;b&gt;클라이언트에서 직접 제어되는지 여부&lt;/b&gt;에 따라 구분됩니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/role_authority-explanation-needed/103703#:~:text=Roles%20are%20something%20that%20exists,so%20another%20computer&quot;&gt;Role_Authority explanation needed. - C++ - Epic Developer Community Forums&lt;/a&gt;) (&lt;a href=&quot;https://forums.unrealengine.com/t/role_authority-explanation-needed/103703#:~:text=computer,controller%2C%20information%20comes%20from%20server&quot;&gt;Role_Authority explanation needed. - C++ - Epic Developer Community Forums&lt;/a&gt;). &lt;b&gt;None&lt;/b&gt;은 복제되지 않음을 의미하여, 네트워크 상에 다른 대응되는 객체가 없음을 뜻합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고:&lt;/b&gt; 과거의 언리얼 엔진(UDK 등)에는 ROLE_DumbProxy 같은 값도 있었으나, Unreal Engine 4 이후에는 사용되지 않으며 일반적으로 위의 네 가지 역할만 사용됩니다 (&lt;a href=&quot;https://beyondunrealwiki.github.io/pages/role.html#:~:text=,These%20actors%20only&quot;&gt;UnrealWiki: Role&lt;/a&gt;) (&lt;a href=&quot;https://forums.unrealengine.com/t/role_authority-explanation-needed/103703#:~:text=enum%20ENetRole%20,ROLE_AutonomousProxy%2C%20ROLE_Authority%2C%20ROLE_MAX%2C&quot;&gt;Role_Authority explanation needed. - C++ - Epic Developer Community Forums&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;액터의 네트워크 상태에 따른 Role 할당&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Actor의 **네트워크 상태(어느 인스턴스에 존재하고 누가 소유하는지)**에 따라 &lt;b&gt;Local Role&lt;/b&gt;과 &lt;b&gt;Remote Role&lt;/b&gt; 값이 달라집니다. 다음은 &lt;b&gt;서버&lt;/b&gt;와 &lt;b&gt;클라이언트&lt;/b&gt;에서 각각 액터의 Role/RemoteRole이 어떻게 설정되는지 정리한 것입니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;서버에서의 역할:&lt;/b&gt; 서버는 게임 세계의 권한을 가지므로, **서버에 존재하는 모든 Actor의 LocalRole은 항상 ROLE_Authority**입니다 (&lt;a href=&quot;https://redchiken.tistory.com/387#:~:text=,%EC%99%B8%EC%9D%98%20Actor%EB%93%A4%EC%97%90%20%EB%8C%80%ED%95%B4%EC%84%9C%EB%8A%94%20ROLE_SimulatedProxy%EA%B0%80%20%EB%90%9C%EB%8B%A4&quot;&gt;[Network] Network Property :: 치킨 날다&lt;/a&gt;). 서버가 액터를 생성(스폰)하거나 소유할 때 기본적으로 자신이 권한을 갖게 됩니다. 이 때 해당 액터가 &lt;b&gt;클라이언트들에게 복제&lt;/b&gt;될 경우를 대비해 &lt;b&gt;RemoteRole&lt;/b&gt;이 설정되는데:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 &lt;b&gt;특정 플레이어가 소유한 액터&lt;/b&gt;(예: 특정 클라이언트의 Pawn/Character 등)라면, 서버에서 그 액터의 RemoteRole을 ROLE_AutonomousProxy로 지정합니다 (&lt;a href=&quot;https://redchiken.tistory.com/387#:~:text=,%EC%99%B8%EC%9D%98%20Actor%EB%93%A4%EC%97%90%20%EB%8C%80%ED%95%B4%EC%84%9C%EB%8A%94%20ROLE_SimulatedProxy%EA%B0%80%20%EB%90%9C%EB%8B%A4&quot;&gt;[Network] Network Property :: 치킨 날다&lt;/a&gt;). 이렇게 지정하면 해당 액터를 소유한 클라이언트에서는 이 액터를 Autonomous Proxy로 취급하게 됩니다. (언리얼 엔진 내부에서는 서버에서 SetAutonomousProxy(true)를 호출하여 클라이언트의 Pawn 등을 AutonomousProxy로 설정하는데, Pawn이 플레이어에게 **Possess(소유)**될 때 이런 처리가 자동으로 이뤄집니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/what-makes-an-actor-autonomous-proxy/449204#:~:text=Any%20actor%20that%20has%20,will%20not%20change%20the%20Role&quot;&gt;What makes an actor Autonomous Proxy? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;).)&lt;/li&gt;
&lt;li&gt;그 외 &lt;b&gt;일반 복제 Actor&lt;/b&gt;(어떤 특정 클라이언트의 소유가 아닌 경우)라면, 서버에서 그 액터의 RemoteRole은 ROLE_SimulatedProxy로 남습니다 (&lt;a href=&quot;https://redchiken.tistory.com/387#:~:text=,%EC%99%B8%EC%9D%98%20Actor%EB%93%A4%EC%97%90%20%EB%8C%80%ED%95%B4%EC%84%9C%EB%8A%94%20ROLE_SimulatedProxy%EA%B0%80%20%EB%90%9C%EB%8B%A4&quot;&gt;[Network] Network Property :: 치킨 날다&lt;/a&gt;). 즉, 해당 액터는 모든 클라이언트에서 단순히 시뮬레이션되는 복제본으로 존재하게 됩니다.&lt;/li&gt;
&lt;li&gt;만약 서버에서만 존재하고 아예 클라이언트에 보내지지 않는 액터라면(RemoteRole이 None), 복제가 비활성화된 상태입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트에서의 역할:&lt;/b&gt; 클라이언트에 복제되어 온 Actor들은 서버에서 설정한 RemoteRole 값에 따라 &lt;b&gt;LocalRole&lt;/b&gt;이 결정됩니다. 서버에서 RemoteRole이 AutonomousProxy로 설정된 액터는 &lt;b&gt;해당 클라이언트를 소유자&lt;/b&gt;로 하여 도착하므로, 그 **클라이언트의 LocalRole이 ROLE_AutonomousProxy**가 됩니다 (&lt;a href=&quot;https://redchiken.tistory.com/387#:~:text=&quot;&gt;[Network] Network Property :: 치킨 날다&lt;/a&gt;). 반면 서버에서 RemoteRole이 SimulatedProxy로 설정된 액터는 **대부분의 클라이언트에서 LocalRole이 ROLE_SimulatedProxy**가 됩니다 (직접 소유하지 않는 한) (&lt;a href=&quot;https://redchiken.tistory.com/387#:~:text=,%EA%B2%BD%EC%9A%B0%EC%97%90%EB%8A%94%20LocalRole%EC%9D%B4%20ROLE_Authority%2C%20RemoteRole%EC%9D%B4%20ROLE_None%EC%9D%B4%EB%8B%A4&quot;&gt;[Network] Network Property :: 치킨 날다&lt;/a&gt;). 요약하면:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;클라이언트 자신이 소유한 액터&lt;/b&gt;: LocalRole = ROLE_AutonomousProxy (해당 클라이언트에서 직접 제어 가능) (&lt;a href=&quot;https://redchiken.tistory.com/387#:~:text=,%EA%B0%80%EB%8A%A5%ED%95%98%EA%B8%B0%20%EB%95%8C%EB%AC%B8%EC%97%90%20LocalRole%EC%9D%B4%20ROLE_SimulatedProxy%EA%B0%80%20%EB%90%9C%EB%8B%A4&quot;&gt;[Network] Network Property :: 치킨 날다&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다른 곳(서버 또는 타 클라이언트)이 소유한 액터&lt;/b&gt;: LocalRole = ROLE_SimulatedProxy (해당 클라이언트에서는 읽기전용 복제본) (&lt;a href=&quot;https://redchiken.tistory.com/387#:~:text=,%EA%B0%80%EB%8A%A5%ED%95%98%EA%B8%B0%20%EB%95%8C%EB%AC%B8%EC%97%90%20LocalRole%EC%9D%B4%20ROLE_SimulatedProxy%EA%B0%80%20%EB%90%9C%EB%8B%A4&quot;&gt;[Network] Network Property :: 치킨 날다&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;이 때 &lt;b&gt;RemoteRole&lt;/b&gt;은 복제된 액터라면 대개 ROLE_Authority로 표시됩니다 (&lt;a href=&quot;https://redchiken.tistory.com/387#:~:text=,%EA%B0%80%EC%A7%80%EA%B8%B0%20%EB%95%8C%EB%AC%B8%EC%97%90%20RemoteRole%EC%9D%B4%20ROLE_Authority%EA%B0%80%20%EB%90%9C%EB%8B%A4&quot;&gt;[Network] Network Property :: 치킨 날다&lt;/a&gt;). 즉, &lt;b&gt;클라이언트는 모든 복제 Actor의 원본 권한이 서버에 있음을 RemoteRole을 통해 인지&lt;/b&gt;하게 됩니다. 이는 앞서 설명한 것처럼 서버의 Role(Auhtority)이 클라이언트에서는 RemoteRole로 보이기 때문입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 전용(Local) 액터&lt;/b&gt;: 만약 어떤 액터가 해당 클라이언트에서만 생성되고 &lt;b&gt;네트워크 복제되지 않을 경우&lt;/b&gt;, 그 액터는 해당 클라이언트에서 &lt;b&gt;LocalRole = ROLE_Authority&lt;/b&gt;로 간주됩니다 (자기 자신만의 권한) (&lt;a href=&quot;https://redchiken.tistory.com/387#:~:text=,%EA%B2%BD%EC%9A%B0%EC%97%90%EB%8A%94%20LocalRole%EC%9D%B4%20ROLE_Authority%2C%20RemoteRole%EC%9D%B4%20ROLE_None%EC%9D%B4%EB%8B%A4&quot;&gt;[Network] Network Property :: 치킨 날다&lt;/a&gt;). 당연히 이 액터는 서버나 다른 클라이언트에는 없으므로 &lt;b&gt;RemoteRole = ROLE_None&lt;/b&gt;입니다 (&lt;a href=&quot;https://redchiken.tistory.com/387#:~:text=,%EA%B2%BD%EC%9A%B0%EC%97%90%EB%8A%94%20LocalRole%EC%9D%B4%20ROLE_Authority%2C%20RemoteRole%EC%9D%B4%20ROLE_None%EC%9D%B4%EB%8B%A4&quot;&gt;[Network] Network Property :: 치킨 날다&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용을 그림으로 정리하면, &lt;b&gt;서버의 Actor&lt;/b&gt;는 항상 Authority이며 &lt;b&gt;RemoteRole로 클라이언트의 역할 상태를 지정&lt;/b&gt;하고, &lt;b&gt;클라이언트의 Actor&lt;/b&gt;는 서버가 Authority임을 RemoteRole로 나타내면서 **LocalRole로 자신이 그 액터를 제어하는지 여부(Autonomous vs Simulated)**를 표시한다고 볼 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;서버와 클라이언트의 Authority 및 Replication 처리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 멀티플레이에서 **서버(Server)**는 기본적으로 &lt;b&gt;모든 복제 Actor의 권한을 가지는 주체&lt;/b&gt;입니다. 따라서 &lt;b&gt;액터의 상태 변경 및 복제 전파&lt;/b&gt;는 서버가 담당하며, 클라이언트는 서버로부터 복제된 데이터를 수신하여 적용합니다 (&lt;a href=&quot;https://redchiken.tistory.com/387#:~:text=,%EC%99%B8%EC%9D%98%20Actor%EB%93%A4%EC%97%90%20%EB%8C%80%ED%95%B4%EC%84%9C%EB%8A%94%20ROLE_SimulatedProxy%EA%B0%80%20%EB%90%9C%EB%8B%A4&quot;&gt;[Network] Network Property :: 치킨 날다&lt;/a&gt;) (&lt;a href=&quot;https://redchiken.tistory.com/387#:~:text=,%EA%B0%80%EC%A7%80%EA%B8%B0%20%EB%95%8C%EB%AC%B8%EC%97%90%20RemoteRole%EC%9D%B4%20ROLE_Authority%EA%B0%80%20%EB%90%9C%EB%8B%A4&quot;&gt;[Network] Network Property :: 치킨 날다&lt;/a&gt;). 이를 &lt;b&gt;Authority 모델&lt;/b&gt;이라고 하며, Unreal Engine은 이 모델을 따릅니다. 핵심 사항은 다음과 같습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;서버만 액터를 복제&lt;/b&gt;합니다. 서버는 자신이 가진 Authority Actor들의 상태를 변경 감지하여 &lt;b&gt;연결된 클라이언트들에게 해당 Actor를 주기적으로 복제(Replicate)하여 전송&lt;/b&gt;합니다 (&lt;a href=&quot;https://coder-nwtma.tistory.com/23#:~:text=%EC%84%9C%EB%B2%84%EB%A7%8C%20%EC%95%A1%ED%84%B0%EB%A5%BC%20%EC%A0%91%EC%86%8D%EB%90%9C%20%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8%EC%97%90%20%EB%A6%AC%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%ED%8A%B8%ED%95%9C%EB%8B%A4&quot;&gt;[UE4] Role, Remote Role&lt;/a&gt;). 반대로 &lt;b&gt;클라이언트는 결코 액터를 서버로 복제 전송하지 않습니다 (&lt;a href=&quot;https://coder-nwtma.tistory.com/23#:~:text=%EC%84%9C%EB%B2%84%EB%A7%8C%20%EC%95%A1%ED%84%B0%EB%A5%BC%20%EC%A0%91%EC%86%8D%EB%90%9C%20%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8%EC%97%90%20%EB%A6%AC%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%ED%8A%B8%ED%95%9C%EB%8B%A4&quot;&gt;[UE4] Role, Remote Role&lt;/a&gt;)&lt;/b&gt;. 즉, 클라이언트에서 어떤 Actor의 프로퍼티를 변경해도 그것이 자동으로 서버의 Actor에 반영되지는 않습니다. 서버로의 변경 전달은 오로지 &lt;b&gt;RPC 호출&lt;/b&gt;이나 &lt;b&gt;서버에 의한 확인&lt;/b&gt;을 통해서만 이뤄집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Authority 여부 판정:&lt;/b&gt; 코드에서 AActor::Role 값을 검사하거나 HasAuthority() 함수를 호출함으로써 현재 액터가 &lt;b&gt;Authority를 가지고 있는지&lt;/b&gt; 알 수 있습니다. HasAuthority()는 내부적으로 Role == ROLE_Authority인지 확인하여 true/false를 리턴하며, Actor가 서버에서 실행 중이거나 (서버 권한) 또는 복제되지 않고 로컬에서만 존재할 때 true가 됩니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/role_authority-explanation-needed/103703#:~:text=Knowing%20that%20and%20the%20order,particle%20for%20all%20of%20them&quot;&gt;Role_Authority explanation needed. - C++ - Epic Developer Community Forums&lt;/a&gt;). 따라서 서버 전용 로직을 실행할 때 흔히 if (HasAuthority()) { ... } 패턴을 사용합니다. (블루프린트에서는 &lt;b&gt;&amp;ldquo;Switch Has Authority&amp;rdquo;&lt;/b&gt; 노드를 사용하여 Authority인 경우와 아닌 경우를 분기합니다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복제 여부 결정:&lt;/b&gt; Actor의 bReplicates 프로퍼티가 true이고 &lt;b&gt;Role이 Authority일 경우&lt;/b&gt;, 엔진은 이 Actor를 복제 대상으로 간주합니다. 구체적으로 **Role == ROLE_Authority이고 RemoteRole이 ROLE_SimulatedProxy 또는 ROLE_AutonomousProxy**로 설정되어 있으면, 해당 엔진 인스턴스(서버)가 그 액터를 원격으로 복제하여 전송하는 역할을 수행하게 됩니다 (&lt;a href=&quot;https://coder-nwtma.tistory.com/23#:~:text=Role%EC%9D%B4%20ROLE_Authority%EB%9D%BC%EB%A9%B4%20%ED%98%84%EC%9E%AC%20%EC%8B%A4%ED%96%89%20%EC%A4%91%EC%9D%B8,Actor%EB%A5%BC%20%EB%8B%B4%EB%8B%B9%20%ED%95%98%EA%B3%A0%20%EC%9E%88%EB%8A%94%20%EA%B2%83%EC%9D%B4%EB%8B%A4&quot;&gt;[UE4] Role, Remote Role&lt;/a&gt;). 반대로 RemoteRole이 None이면 복제되지 않습니다. 요컨대 &lt;b&gt;서버에서 Authority인 액터만이 네트워크 상에 자신의 복제본을 가질 수 있고, RemoteRole 값이 그 복제본들의 형태를 정의&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트 권한 및 제약:&lt;/b&gt; 클라이언트는 Authority가 아닌 Actor(즉, SimulatedProxy나 AutonomousProxy)에 대해서는 &lt;b&gt;직접 상태를 변경하거나 서버에 영향을 줄 수 없습니다&lt;/b&gt; (&lt;a href=&quot;https://velog.io/@dltmdrl1244/UE5-Actor-Role-Remote-Role#:~:text=&quot;&gt;[UE5] Actor Role, Remote Role&lt;/a&gt;). AutonomousProxy의 경우 플레이어 입력을 통해 이동 등을 변경하지만, 이 또한 결국 &lt;b&gt;서버에 RPC로 요청&lt;/b&gt;하여 서버의 Authority Actor에 적용되고 다시 복제되는 식으로 동작합니다. SimulatedProxy는 아예 RPC 호출 권한이 없고, 완전히 수동적으로 서버 업데이트를 따릅니다. 정리하면, &lt;b&gt;실질적인 게임 로직의 권한은 서버에 집중&lt;/b&gt;되며, 클라이언트는 소유한 액터에 한해 제한적으로 상호작용(예: 입력 처리 및 서버 요청)을 하는 것입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Owner과 Role의 관계:&lt;/b&gt; Actor의 Owner는 RPC 호출 권한 및 시각화 범위 등에서 사용되는 개념으로, &lt;b&gt;어떤 클라이언트를 Actor의 소유자로 지정하면 그 클라이언트는 해당 Actor에 대한 RPC를 서버로 보낼 수 있는 권한&lt;/b&gt;을 얻게 됩니다. 하지만 Owner를 설정하는 것만으로 Role 값이 자동으로 AutonomousProxy로 바뀌지는 않습니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/what-makes-an-actor-autonomous-proxy/449204#:~:text=input%20to%20be%20handled%20correctly,will%20not%20change%20the%20Role&quot;&gt;What makes an actor Autonomous Proxy? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). &lt;b&gt;AutonomousProxy로의 전환은 서버 측에서 명시적으로 SetAutonomousProxy(true)를 호출&lt;/b&gt;해야 이루어지며, 언리얼 엔진은 Pawn이 플레이어에 의해 Possess될 때 이 함수를 호출하여 Pawn 및 관련된 Actor들을 AutonomousProxy로 설정합니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/what-makes-an-actor-autonomous-proxy/449204#:~:text=Any%20actor%20that%20has%20,will%20not%20change%20the%20Role&quot;&gt;What makes an actor Autonomous Proxy? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). 이를 통해 해당 플레이어의 클라이언트에서 그 Actor가 LocalRole=AutonomousProxy로 동작하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Role 관련 코드 사용 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 Role 개념은 게임 코드에서 조건 분기나 함수 호출을 통해 활용됩니다. 아래에 간단한 C++ 예시와 함께 설명을 제공합니다:&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;void AMyActor::BeginPlay()
{
    Super::BeginPlay();

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

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

    // 예시 3: 모든 클라이언트에서 실행할 로직
    if (GetLocalRole() &amp;lt; ROLE_Authority)
    {
        // Role &amp;lt; Authority 즉, 클라이언트(AutonomousProxy 또는 SimulatedProxy)에서만 실행
        PlayClientFX();  // 예: 모든 클라이언트에 이펙트 재생
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 HasAuthority()를 사용한 부분은 &lt;b&gt;서버에서만&lt;/b&gt; 실행되어야 하는 코드를 보호합니다. 이는 곧 Role == ROLE_Authority인지를 확인하는 것과 동일하며 (&lt;a href=&quot;https://forums.unrealengine.com/t/role_authority-explanation-needed/103703#:~:text=Knowing%20that%20and%20the%20order,particle%20for%20all%20of%20them&quot;&gt;Role_Authority explanation needed. - C++ - Epic Developer Community Forums&lt;/a&gt;), 예를 들어 &lt;b&gt;게임 시작 초기화나 주요 게임 로직은 서버에서만 수행&lt;/b&gt;되도록 보장할 때 사용합니다. 두 번째 분기에서는 GetLocalRole() 값이 ROLE_AutonomousProxy인지 확인하여, &lt;b&gt;자신이 소유한 액터에서만 수행되는 클라이언트 전용 로직&lt;/b&gt;을 넣고 있습니다. 예를 들어 각 플레이어 클라이언트에서 자기 캐릭터에 대해서만 UI를 표시하거나 입력을 받는 처리를 할 때 이런 체크를 활용합니다. 세 번째 if 분기는 GetLocalRole() &amp;lt; ROLE_Authority를 조건으로 사용했는데, 이는 &lt;b&gt;현재 코드가 클라이언트 측에서 실행&lt;/b&gt;되고 있음을 뜻합니다 (ROLE_AutonomousProxy 또는 ROLE_SimulatedProxy일 경우 해당) (&lt;a href=&quot;https://forums.unrealengine.com/t/role_authority-explanation-needed/103703#:~:text=HasAuthority%20pretty%20much.%20Role%20,particle%20for%20all%20of%20them&quot;&gt;Role_Authority explanation needed. - C++ - Epic Developer Community Forums&lt;/a&gt;). 이런 조건을 통해 &lt;b&gt;모든 클라이언트에서 실행되어야 하는 효과&lt;/b&gt; 등을 일괄 처리할 수 있습니다. 예를 들어 서버에서 어떤 이벤트 발생 시, 서버에서 멀티캐스트 RPC를 호출하지 않고 각 클라이언트의 Tick에서 Role &amp;lt; Authority 조건으로 효과를 재생하게 할 수도 있습니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/role_authority-explanation-needed/103703#:~:text=HasAuthority%20pretty%20much.%20Role%20,particle%20for%20all%20of%20them&quot;&gt;Role_Authority explanation needed. - C++ - Epic Developer Community Forums&lt;/a&gt;).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 예로, **블루프린트(Blueprint)**에서는 Authority 핀과 Remote 핀을 가진 &lt;b&gt;&amp;ldquo;Switch Has Authority&amp;rdquo;&lt;/b&gt; 노드를 제공하여, 현재 실행 컨텍스트가 서버인지 클라이언트인지를 쉽게 분기할 수 있습니다. 이를 통해 C++의 if (HasAuthority())와 유사하게 서버 전용 vs 클라이언트 전용 동작을 구현합니다. 또한 블루프린트에서 GetLocalRole/GetRemoteRole 노드를 사용하면 현재 액터의 역할을 열거형으로 얻을 수 있으며, 이를 화면에 출력하거나 로직 분기에 활용할 수 있습니다 (예: 디버그 목적으로 플레이어 캐릭터 머리 위에 &amp;ldquo;Authority/Autonomous/Simulated&amp;rdquo; 등을 표시) (&lt;a href=&quot;https://velog.io/@dltmdrl1244/UE5-Actor-Role-Remote-Role#:~:text=ENetRole%20LocalRole%20%3D%20InPawn,GetRemoteRole%28%29%3B%20FString%20Role&quot;&gt;[UE5] Actor Role, Remote Role&lt;/a&gt;) (&lt;a href=&quot;https://velog.io/@dltmdrl1244/UE5-Actor-Role-Remote-Role#:~:text=FString%20LocalRoleString%20%3D%20FString%3A%3APrintf%28TEXT%28,%2ARole%29%3B%20SetDisplayText%28LocalRoleString%29%3B%20%2F%2FSetDisplayText%28RemoteRoleString%29%3B&quot;&gt;[UE5] Actor Role, Remote Role&lt;/a&gt;).&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리 및 참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진의 Role 개념은 &lt;b&gt;네트워크 플레이에서 매우 중요한 권한 관리 메커니즘&lt;/b&gt;입니다. &lt;b&gt;Local Role&lt;/b&gt;은 액터가 현재 실행 중인 머신에서 어떤 권한 상태인지를 나타내고, &lt;b&gt;Remote Role&lt;/b&gt;은 반대편에 그 액터가 어떤 상태로 존재하는지 알려줍니다 (&lt;a href=&quot;https://iiii4.tistory.com/146#:~:text=%2A%20,%EA%B0%9D%EC%B2%B4%EA%B0%80%20%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C%20%EC%A0%9C%EC%96%B4%EB%90%98%EC%A7%80%20%EC%95%8A%EA%B3%A0%20%EC%8B%9C%EB%AE%AC%EB%A0%88%EC%9D%B4%EC%85%98%EB%90%A9%EB%8B%88%EB%8B%A4&quot;&gt;[UE5] Network Role&lt;/a&gt;). 서버는 항상 Authority 역할을 가지며 액터 상태의 **진실된 소스(authoritative source)**가 되고, 클라이언트는 서버의 결정을 &lt;b&gt;복제받아 반영&lt;/b&gt;하거나 자신이 소유한 액터에 한해 &lt;b&gt;예측 수행&lt;/b&gt; 및 &lt;b&gt;서버에 요청&lt;/b&gt;을 할 수 있습니다. Role과 RemoteRole 값을 올바르게 이해하고 사용하면, 어디서 어떤 코드를 실행해야 할지, 어떤 액터를 누구가 변경할 수 있을지, RPC를 어떻게 보낼지 등을 효과적으로 제어할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고 자료:&lt;/b&gt; 언리얼 엔진 공식 문서 &amp;ndash; Networking - Actors - Roles (액터 롤 및 리모트 롤) (&lt;a href=&quot;https://coder-nwtma.tistory.com/23#:~:text=%ED%8A%B9%EC%A0%95%20Actor%EC%9D%98%20Authority%EB%A5%BC%20%EC%95%8C%EC%95%84%EB%82%B4%EA%B8%B0%20%EC%9C%84%ED%95%B4%EC%84%9C%EB%8A%94,Role%EC%9D%B4%20ROLE_Authority%EC%9D%B8%EC%A7%80%20%EB%B4%90%EC%95%BC%20%ED%95%9C%EB%8B%A4&quot;&gt;[UE4] Role, Remote Role&lt;/a&gt;) (&lt;a href=&quot;https://coder-nwtma.tistory.com/23#:~:text=Role%EA%B3%BC%20RemoteRole%EC%9D%80%20%EB%88%84%EA%B0%80%20%EC%A1%B0%EC%82%AC%ED%95%98%EB%8A%94%EA%B0%80%EC%97%90%20%EB%94%B0%EB%9D%BC,%EA%B0%92%EC%9D%B4%20%EB%B0%98%EB%8C%80%EA%B0%80%20%EB%90%A0%20%EC%88%98%20%EC%9E%88%EB%8B%A4&quot;&gt;[UE4] Role, Remote Role&lt;/a&gt;), 언리얼 엔진 포럼 및 위키 설명 (&lt;a href=&quot;https://forums.unrealengine.com/t/role_authority-explanation-needed/103703#:~:text=Roles%20are%20something%20that%20exists,so%20another%20computer&quot;&gt;Role_Authority explanation needed. - C++ - Epic Developer Community Forums&lt;/a&gt;) (&lt;a href=&quot;https://beyondunrealwiki.github.io/pages/role.html#:~:text=,be%20replicated%20to%20any%20other&quot;&gt;UnrealWiki: Role&lt;/a&gt;), 그리고 언리얼 개발자들의 블로그 자료 (&lt;a href=&quot;https://redchiken.tistory.com/387#:~:text=,%EC%99%B8%EC%9D%98%20Actor%EB%93%A4%EC%97%90%20%EB%8C%80%ED%95%B4%EC%84%9C%EB%8A%94%20ROLE_SimulatedProxy%EA%B0%80%20%EB%90%9C%EB%8B%A4&quot;&gt;[Network] Network Property :: 치킨 날다&lt;/a&gt;) (&lt;a href=&quot;https://velog.io/@dltmdrl1244/UE5-Actor-Role-Remote-Role#:~:text=Actor%20Role%EA%B3%BC%20Remote%20Role%EC%9D%80%20,enum%20%ED%81%B4%EB%9E%98%EC%8A%A4%EB%A1%9C%20%ED%91%9C%ED%98%84%EB%90%9C%EB%8B%A4&quot;&gt;[UE5] Actor Role, Remote Role&lt;/a&gt;) 등을 참고하여 작성했습니다. Role의 개념과 동작 방식에 대한 정확한 이해는 안정적인 멀티플레이 게임플레이 구현에 필수적입니다. 각 개발 상황에 맞게 Role을 활용하여 권한이 필요한 로직은 서버에서, 부가 연출은 클라이언트에서 수행하도록 설계하시기 바랍니다.&lt;/p&gt;</description>
      <category>UnraealEngine</category>
      <author>남찬우</author>
      <guid isPermaLink="true">https://kakaoncw.tistory.com/99</guid>
      <comments>https://kakaoncw.tistory.com/99#entry99comment</comments>
      <pubDate>Thu, 27 Mar 2025 21:19:18 +0900</pubDate>
    </item>
    <item>
      <title>Data Asset vs Primary Data Asset</title>
      <link>https://kakaoncw.tistory.com/97</link>
      <description>&lt;h1&gt;언리얼 엔진 Data Asset vs Primary Data Asset 비교&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진에서는 게임 속 다양한 &lt;b&gt;데이터&lt;/b&gt;를 에셋 형태로 관리하기 위해 &lt;b&gt;Data Asset&lt;/b&gt;과 &lt;b&gt;Primary Data Asset&lt;/b&gt; 개념을 제공합니다. 아래에서는 두 개념의 차이점, 사용 방법, 그리고 실전 활용 예시와 최적화 팁 등을 정리합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Data Asset과 Primary Data Asset의 사용 목적 및 차이점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Data Asset&lt;/b&gt;(데이터 에셋)은 UDataAsset 기반 클래스로, 게임의 각종 설정 정보나 리소스 데이터를 담아 에셋으로 저장하는 객체입니다 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,%EB%8D%9C%20%EC%A4%91%EC%9A%94%ED%95%9C%20%EC%9A%94%EC%86%8C%EB%93%A4%EC%97%90%20%EC%A3%BC%EB%A1%9C%20%EC%82%AC%EC%9A%A9%ED%95%9C%EB%8B%A4&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;). 보통 게임의 핵심 로직이 아닌 비교적 &lt;b&gt;덜 중요한 시스템 요소나 구성 데이터를 저장&lt;/b&gt;하는 데 활용하며, UObject를 상속받아 필요한 속성을 UPROPERTY로 정의합니다 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,%EB%8D%9C%20%EC%A4%91%EC%9A%94%ED%95%9C%20%EC%9A%94%EC%86%8C%EB%93%A4%EC%97%90%20%EC%A3%BC%EB%A1%9C%20%EC%82%AC%EC%9A%A9%ED%95%9C%EB%8B%A4&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;). Data Asset 인스턴스 하나하나는 &lt;b&gt;데이터 테이블(Data Table)의 하나의 행에 해당하는 독립적 데이터 항목&lt;/b&gt;처럼 볼 수 있습니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/15jpvlc/dataassets_are_incredibly_useful/#:~:text=Data%20assets%20is%20an%20uboject,single%20entry%20in%20data%20table&quot;&gt;DataAssets are incredibly useful : r/unrealengine&lt;/a&gt;). 예를 들어 무기 속성, 캐릭터 능력치, 아이템 설정값 등을 개별 Data Asset으로 만들어 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Primary Data Asset&lt;/b&gt;(프라이머리 데이터 에셋)은 UPrimaryDataAsset 기반 클래스로, Data Asset과 동일하게 데이터를 담지만 &lt;b&gt;고유한 Primary Asset ID를 통해 엔진의 Asset Manager에서 직접 인식하고 관리&lt;/b&gt;할 수 있는 에셋 유형입니다 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,Asset%EC%9C%BC%EB%A1%9C%20%EB%A7%8C%EB%93%9C%EB%A0%A4%EB%A9%B4%20GetPrimaryAssetId%20%ED%95%A8%EC%88%98%EB%A5%BC%20override%ED%95%98%EC%97%AC&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;). Primary Data Asset은 내부적으로 GetPrimaryAssetId 함수를 구현하여 자신만의 **FPrimaryAssetId(Primary Asset ID)**를 가지며, 이 ID는 **두 부분(Primary Asset Type과 Asset Name)**으로 구성됩니다 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=%EC%9C%A0%ED%9A%A8%ED%95%9C%20FPrimaryAssetId%20%EB%A5%BC%20%EB%B0%98%ED%99%98%ED%95%B4%EC%95%BC%20%ED%95%9C%EB%8B%A4,Bundle%EC%9D%84%20%EC%A7%80%EC%9B%90%ED%95%98%EB%8A%94%20Asset%20Manager%EC%97%90%EC%84%9C%20%EC%88%98%EB%8F%99%EC%9C%BC%EB%A1%9C&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;). Asset Manager는 이 ID를 활용해 해당 에셋을 &lt;b&gt;로드/언로드하거나 일괄 처리&lt;/b&gt;할 수 있습니다 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,%EC%97%90%EC%84%9C%20%EB%B0%98%ED%99%98%ED%95%98%EB%8A%94%20%EA%B0%92%EA%B3%BC%20%EA%B0%99%EC%95%84%EC%95%BC%20%ED%95%9C%EB%8B%A4&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;). 기본적으로 언리얼 엔진에서는 맵(UWorld)과 같은 일부 에셋만 Primary Asset으로 간주되는데, Data Asset을 Primary Asset으로 취급하려면 GetPrimaryAssetId를 오버라이드하여 유효한 ID를 반환하거나 UPrimaryDataAsset을 상속해야 합니다 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,Asset%EC%9C%BC%EB%A1%9C%20%EB%A7%8C%EB%93%9C%EB%A0%A4%EB%A9%B4%20GetPrimaryAssetId%20%ED%95%A8%EC%88%98%EB%A5%BC%20override%ED%95%98%EC%97%AC&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;). 요약하면 &lt;b&gt;Primary Data Asset = Data Asset + Asset Manager 지원 기능&lt;/b&gt;이라고 볼 수 있습니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1e1wq3n/primary_data_assets_only_have_exactly_1_instance/#:~:text=assets%20won%27t%20be%20loaded%20until,you%20manually%20load%20them&quot;&gt;Primary Data assets only have exactly 1 instance stored in memory, right? : r/unrealengine&lt;/a&gt;).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개념의 &lt;b&gt;주요 차이점&lt;/b&gt;은 다음과 같습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Asset Manager 연동 여부&lt;/b&gt;: Primary Data Asset은 &lt;b&gt;Asset Manager가 인지&lt;/b&gt;하는 에셋으로, 게임 실행 중에 Primary Asset ID로 직접 에셋을 찾아 로드하거나 언로드할 수 있습니다 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,%EC%97%90%EC%84%9C%20%EB%B0%98%ED%99%98%ED%95%98%EB%8A%94%20%EA%B0%92%EA%B3%BC%20%EA%B0%99%EC%95%84%EC%95%BC%20%ED%95%9C%EB%8B%A4&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;). 반면 일반 Data Asset은 Asset Manager의 관리 대상이 아니므로, 다른 에셋에 참조되거나 개발자가 수동으로 로드할 때만 사용됩니다. 즉, Primary Data Asset들은 Asset Manager를 통해 &lt;b&gt;전역적으로 식별&lt;/b&gt;되고 컬렉션처럼 관리되며, Data Asset들은 &lt;b&gt;개별 객체&lt;/b&gt;로 존재합니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/15jpvlc/dataassets_are_incredibly_useful/#:~:text=Data%20assets%20is%20an%20uboject,single%20entry%20in%20data%20table&quot;&gt;DataAssets are incredibly useful : r/unrealengine&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;에셋 번들(Asset Bundle) 지원&lt;/b&gt;: Primary Data Asset은 에셋 번들을 지원하여 하나의 에셋 내에서도 필요한 부분만 로드하는 세분화가 가능합니다 (&lt;a href=&quot;https://www.jooballin.com/p/unreal-engine-the-asset-manager-primary#:~:text=,on%20the%20type%20and%20asset&quot;&gt;Unreal Engine - The Asset Manager, Primary Assets and Asset Bundles&lt;/a&gt;). 예를 들어 Primary Data Asset에 Soft Object로 참조된 리소스들을 &amp;ldquo;로비용&amp;rdquo;, &amp;ldquo;인게임용&amp;rdquo; 번들로 나누어두고 상황에 따라 선택적으로 불러올 수 있습니다. 일반 Data Asset은 특별한 설정 없이는 자신이 참조하는 에셋을 모두 한 번에 불러옵니다. (Primary Data Asset이 아닌 경우라도 GetPrimaryAssetId를 직접 구현하고 Asset Manager 설정을 하면 번들 기능을 활용할 수 있습니다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용 용도&lt;/b&gt;: Data Asset은 소규모 데이터나 항상 필요한 데이터를 간단히 저장할 때 주로 쓰입니다. Primary Data Asset은 &lt;b&gt;규모가 큰 게임의 리소스 관리&lt;/b&gt;, &lt;b&gt;DLC/모듈화&lt;/b&gt;, &lt;b&gt;필요한 자원의 동적 로드&lt;/b&gt; 등에 적합합니다. Primary Data Asset 여러 개를 한데 모아 &lt;b&gt;일종의 데이터베이스처럼 활용&lt;/b&gt;할 수 있으며, 이 점에서 개별 구조체 목록인 Data Table과 비교해 &lt;b&gt;Data Asset들의 컬렉션&lt;/b&gt; 역할을 합니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/15jpvlc/dataassets_are_incredibly_useful/#:~:text=Data%20assets%20is%20an%20uboject,single%20entry%20in%20data%20table&quot;&gt;DataAssets are incredibly useful : r/unrealengine&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면, &lt;b&gt;Data Asset&lt;/b&gt;은 주로 정적인 설정 데이터를 간편히 에셋화한 것이고, &lt;b&gt;Primary Data Asset&lt;/b&gt;은 그러한 데이터 에셋을 &lt;b&gt;분류하고 관리&lt;/b&gt;하기 위한 ID 체계를 갖춰 &lt;b&gt;대규모 프로젝트의 자산 관리와 최적화&lt;/b&gt;를 돕는다는 차이가 있습니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1e1wq3n/primary_data_assets_only_have_exactly_1_instance/#:~:text=typically%20used%20for%20containing%20large,until%20you%20manually%20load%20them&quot;&gt;Primary Data assets only have exactly 1 instance stored in memory, right? : r/unrealengine&lt;/a&gt;). 프로젝트 규모와 필요에 따라 적절한 방식을 선택하면 됩니다 (예: 작은 게임에서는 그냥 Data Asset으로도 충분하며, 방대한 콘텐츠가 있는 게임에서는 Primary Data Asset으로 관리하면 유리).&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;생성 및 등록 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;에디터에서의 생성 절차&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Data Asset 생성 (인스턴스)&lt;/b&gt;: C++로 정의된 Data Asset 클래스가 있으면, 언리얼 에디터의 콘텐츠 브라우저에서 **우클릭 -&amp;gt; Miscellaneous -&amp;gt; Data Asset**을 선택해 새로운 데이터 에셋을 만들 수 있습니다. 나타나는 클래스 선택 창에서 생성하려는 Data Asset의 클래스를 고르면 해당 클래스의 인스턴스 에셋(.uasset 파일)이 생성됩니다 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,%ED%8F%AC%ED%95%A8%EB%90%98%EC%A7%80%20%EC%95%8A%EC%9D%80%20Blueprint%20%ED%95%A8%EC%88%98%20%EC%82%AC%EC%9A%A9&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;). 이렇게 생성된 Data Asset 에셋은 &lt;b&gt;해당 클래스의 객체 인스턴스&lt;/b&gt;로서 콘텐츠에 저장됩니다 (Blueprint 클래스 자체를 만드는 것이 아닙니다) (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,Asset%EC%9D%98%20Class%EC%97%90%20Access%ED%95%98%EB%8A%94%20%EB%B0%A9%EB%B2%95&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;). 따라서 생성된 Data Asset은 &lt;b&gt;별도 인스턴스를 게임 중에 스폰하지 않아도&lt;/b&gt; 바로 그 객체를 통해 데이터를 읽을 수 있습니다 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,Asset%EC%9D%98%20Class%EC%97%90%20Access%ED%95%98%EB%8A%94%20%EB%B0%A9%EB%B2%95&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Blueprint를 통한 생성&lt;/b&gt;: C++ 코딩 없이 Data Asset을 만들 수도 있습니다. &lt;b&gt;블루프린트 클래스&lt;/b&gt;로 Data Asset을 생성하려면, &lt;b&gt;Add New -&amp;gt; Blueprint Class&lt;/b&gt;를 선택하고, 모든 클래스 목록에서 PrimaryDataAsset (혹은 DataAsset)을 부모 클래스로 하는 블루프린트를 만들면 됩니다. 이렇게 하면 데이터 전용 블루프린트 클래스가 생성되는데, 여기에 변수들을 정의하고 기본값을 채워 넣어 &lt;b&gt;데이터 전용 자산&lt;/b&gt;으로 활용할 수 있습니다. 또는, C++ 기반의 UPrimaryDataAsset 하위 클래스를 만들어 두고 이를 부모로 하여 Blueprint를 파생시키는 방법도 있습니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/setup-primary-assets-via-blueprint-for-asset-manager-scan/559269#:~:text=Step%201%3A%20Create%20a%20new,give%20it%20a%20name%20PDA_Item&quot;&gt;Setup primary assets via blueprint for asset manager scan - Blueprint - Epic Developer Community Forums&lt;/a&gt;). 단, 블루프린트만으로 Primary Data Asset을 완전히 활용하려면 약간의 설정이 필요한데, 이는 아래 Asset Manager 설정 부분에서 함께 설명합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C++ 클래스 기반 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Data Asset/Primary Data Asset 클래스 작성&lt;/b&gt;: Data Asset을 사용하려면 우선 해당 데이터를 담을 C++ 클래스가 필요합니다. Unreal Editor의 &lt;b&gt;새 C++ 클래스 추가&lt;/b&gt; 기능으로 DataAsset을 베이스로 선택하여 클래스를 생성할 수 있습니다. 생성된 클래스 헤더에서 UDataAsset 또는 UPrimaryDataAsset를 상속하고, 필요한 프로퍼티를 UPROPERTY로 선언합니다. 이 클래스에는 UCLASS(BlueprintType) 매크로를 지정하여 에디터에서 에셋으로 다룰 수 있게 해야 합니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/correct-way-of-using-dataasset-for-configuration/491928#:~:text=UCLASS,GENERATED_BODY&quot;&gt;Correct way of using DataAsset for configuration - C++ - Epic Developer Community Forums&lt;/a&gt;). Primary Data Asset으로 사용할 경우 GetPrimaryAssetId 함수를 오버라이드하여 고유 ID를 반환하거나, UPrimaryDataAsset을 상속받음으로써 기본 동작을 사용할 수 있습니다. 아래는 예시 코드입니다:&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// UPrimaryDataAsset 상속 예시 (아이템 데이터 에셋)
UCLASS(BlueprintType)
class MYGAME_API UItemDataAsset : public UPrimaryDataAsset
{
    GENERATED_BODY()
public:
    // 아이템 속성 데이터 선언
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=&quot;Item&quot;)
    FText ItemName;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=&quot;Item&quot;)
    float Damage;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=&quot;Item&quot;)
    UTexture2D* Icon;

    // Primary Asset ID 설정 (Primary Asset Type: &quot;Item&quot;, Name: 에셋 이름)
    virtual FPrimaryAssetId GetPrimaryAssetId() const override
    {
        return FPrimaryAssetId(TEXT(&quot;Item&quot;), GetFName()); // Type &quot;Item&quot;, Name은 에셋 이름
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 클래스를 정의하고 컴파일하면, 에디터에서 해당 클래스로 &lt;b&gt;데이터 에셋 인스턴스&lt;/b&gt;를 생성할 수 있습니다. UPrimaryDataAsset을 상속할 경우 기본적으로 GetPrimaryAssetId가 해당 클래스명을 PrimaryAssetType으로 하는 ID를 반환하지만, 필요에 따라 위 예시처럼 오버라이드하여 &lt;b&gt;Type 문자열을 커스텀&lt;/b&gt;할 수 있습니다. (예를 들어 Type을 &quot;Item&quot; 등으로 지정하여 논리적인 그룹화를 할 수 있음)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Primary Data Asset 등록 및 설정 (Asset Manager)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Primary Data Asset을 제대로 활용하려면 프로젝트 설정에서 Asset Manager에 해당 Asset을 &lt;b&gt;등록&lt;/b&gt;해야 합니다. 언리얼 에디터에서 &lt;b&gt;Edit -&amp;gt; Project Settings -&amp;gt; Asset Manager&lt;/b&gt;로 이동하여 아래와 같이 설정합니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Primary Asset Types to Scan&lt;/b&gt; 섹션에서 &lt;b&gt;+ 추가&lt;/b&gt; 버튼을 눌러 새로운 Primary Asset 유형을 등록합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Primary Asset Type Name&lt;/b&gt;에 Type 식별자를 입력합니다. (예: &quot;Item&quot; 등, 위의 GetPrimaryAssetId에서 반환하는 Type과 동일한 문자열이어야 합니다 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,%EC%97%90%EC%84%9C%20%EB%B0%98%ED%99%98%ED%95%98%EB%8A%94%20%EA%B0%92%EA%B3%BC%20%EA%B0%99%EC%95%84%EC%95%BC%20%ED%95%9C%EB%8B%A4&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;).)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Asset Base Class&lt;/b&gt;에는 해당 에셋들의 베이스 클래스를 지정합니다. C++ 클래스인 UItemDataAsset이나 블루프린트로 만든 경우 그 블루프린트 클래스를 선택합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Directories&lt;/b&gt;에는 이 에셋들이 들어있는 콘텐츠 폴더 경로를 지정합니다 (예: /Game/Items). 해당 폴더 내의 자산들이 스캔되어 Asset Manager에 등록됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Has Blueprint Classes&lt;/b&gt; 옵션은 데이터 자산이 블루프린트 클래스 자체인지 여부를 나타냅니다. 우리가 일반적으로 만드는 Data Asset 인스턴스들은 &lt;b&gt;블루프린트 클래스가 아닌 객체 인스턴스&lt;/b&gt;이므로 이 옵션을 끄는 것이 일반적입니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/setup-primary-assets-via-blueprint-for-asset-manager-scan/559269#:~:text=,%2FGame%2FItems&quot;&gt;Setup primary assets via blueprint for asset manager scan - Blueprint - Epic Developer Community Forums&lt;/a&gt;). (만약 Primary Asset으로 블루프린트 클래스를 직접 쓰는 경우에는 켜고, Type Name에 _C가 붙은 클래스로 인식시켜야 합니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/setup-primary-assets-via-blueprint-for-asset-manager-scan/559269#:~:text=,%2FGame%2FItems&quot;&gt;Setup primary assets via blueprint for asset manager scan - Blueprint - Epic Developer Community Forums&lt;/a&gt;).)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정을 마쳤다면, 에디터를 재시작하거나 Tools -&amp;gt; Refresh Asset Manager 등을 실행하여 Asset Manager가 새 Primary Asset들을 스캔하게 합니다. &lt;b&gt;주의&lt;/b&gt;: Primary Asset Type 이름은 반드시 GetPrimaryAssetId()가 반환하는 Type과 일치해야 하며 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,%EC%97%90%EC%84%9C%20%EB%B0%98%ED%99%98%ED%95%98%EB%8A%94%20%EA%B0%92%EA%B3%BC%20%EA%B0%99%EC%95%84%EC%95%BC%20%ED%95%9C%EB%8B%A4&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;), 해당 폴더에 에셋이 존재해야 올바르게 등록됩니다. 또한 Primary Data Asset들을 패키징할 때 누락되지 않도록 기본적으로 위 설정에 따라 자동으로 쿠킹되지만(Always Cook), 필요한 경우 개별 에셋의 Cook Rule을 조정할 수도 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;게임에서의 활용 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 아이템 데이터 관리&lt;/b&gt;: RPG나 액션 게임의 &lt;b&gt;무기, 방어구, 소비품&lt;/b&gt; 등 아이템들을 Data Asset으로 정의할 수 있습니다. 예를 들어 WeaponDataAsset을 만들어 무기 이름, 공격력, 메쉬, 이펙트, 아이콘 텍스처 등 정보를 담아두고, 캐릭터가 아이템을 장착할 때 해당 Data Asset을 참조하여 속성을 적용합니다. Epic Games의 Action RPG 샘플에서도 모든 아이템을 Data Asset으로 관리하며, 이를 Primary Data Asset으로 등록해 Asset Manager로 로딩하도록 구현되어 있습니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/15jpvlc/dataassets_are_incredibly_useful/#:~:text=This%20is%20definitely%20the%20recommended,promoting%20Weapon%20to%20a%20PrimaryDataAsset&quot;&gt;DataAssets are incredibly useful : r/unrealengine&lt;/a&gt;). Primary Data Asset으로 설계하면 인벤토리 시스템에서 &lt;b&gt;아이템 참조를 Primary Asset ID로 통일&lt;/b&gt;하여 관리할 수 있고, 필요 시 특정 아이템군만 메모리에 로드하거나 언로드할 수 있어 유연합니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/15jpvlc/dataassets_are_incredibly_useful/#:~:text=This%20is%20definitely%20the%20recommended,promoting%20Weapon%20to%20a%20PrimaryDataAsset&quot;&gt;DataAssets are incredibly useful : r/unrealengine&lt;/a&gt;). 아래는 무기 데이터 에셋에 들어갈 수 있는 정보의 예시입니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무기 이름, 설명 텍스트&lt;/li&gt;
&lt;li&gt;공격력, 사거리 등 밸런스 수치&lt;/li&gt;
&lt;li&gt;무기 메쉬에 대한 소프트 객체 참조&lt;/li&gt;
&lt;li&gt;공격 시 발생하는 이펙트나 사운드에 대한 참조&lt;/li&gt;
&lt;li&gt;UI에 표시할 아이콘 이미지와 등급(Rarity) 등의 메타데이터&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 한 &lt;b&gt;Weapon DataAsset&lt;/b&gt;에서는 무기 액터 클래스, 데미지 타입, 아이콘 텍스처, UI 표시 이름, 레어도 등의 변수를 포함하도록 구성할 수 있습니다 (&lt;a href=&quot;https://www.tomlooman.com/unreal-engine-asset-manager-async-loading/#:~:text=Another%20example%20of%20a%20Primary,texture%2C%20UI%20Name%2C%20Rarity%2C%20etc&quot;&gt;Asset Manager for Data Assets &amp;amp; Async Loading - Tom Looman&lt;/a&gt;). 이렇게 정의된 여러 무기 Data Asset들을 Primary Asset Type &amp;ldquo;Weapon&amp;rdquo;으로 묶어두면, &lt;b&gt;Asset Manager를 통해 일괄 로드/언로드하거나 조회&lt;/b&gt;할 수 있어 대규모 아이템을 가진 게임에서도 효율적으로 다룰 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 몬스터/캐릭터 설정&lt;/b&gt;: 다양한 적 몬스터나 캐릭터 정보를 Data Asset으로 관리할 수도 있습니다. 예를 들어 &lt;b&gt;MonsterData&lt;/b&gt;라는 Primary Data Asset 클래스(위 예시의 UPrimaryDataAsset 상속)를 만들어, 각 적 유형별 Data Asset 인스턴스를 생성합니다. 해당 Data Asset에는 스폰할 몬스터의 Pawn/Character 블루프린트 클래스, 체력/공격력 같은 능력치, 사용할 스킬 목록, AI 비헤이비어트리 애셋, 표시 이름과 초상화 이미지 등 관련 정보를 모두 담아둘 수 있습니다 (&lt;a href=&quot;https://www.tomlooman.com/unreal-engine-asset-manager-async-loading/#:~:text=An%20example%20of%20a%20PrimaryAsset,stuff%20like%20name%20and%20icon&quot;&gt;Asset Manager for Data Assets &amp;amp; Async Loading - Tom Looman&lt;/a&gt;). 게임 로직에서는 몬스터를 스폰할 때 이 Data Asset을 참조하여 어떤 클래스와 능력을 사용할지 결정하게 됩니다. 특히 Primary Data Asset으로 지정된 몬스터 데이터는 Asset Manager에 의해 필요 시에만 로드될 수 있어서, 특정 스테이지의 몬스터들만 메모리에 올리는 최적화도 가능합니다. &lt;b&gt;Tom Looman&lt;/b&gt;의 액션 로그라이크 샘플에서는 이러한 Primary Data Asset을 사용하여 몬스터의 설정과 해당 몬스터가 가질 액터/액션들을 관리하고 있습니다 (&lt;a href=&quot;https://www.tomlooman.com/unreal-engine-asset-manager-async-loading/#:~:text=An%20example%20of%20a%20PrimaryAsset,stuff%20like%20name%20and%20icon&quot;&gt;Asset Manager for Data Assets &amp;amp; Async Loading - Tom Looman&lt;/a&gt;).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 그 외 활용 사례&lt;/b&gt;: 이외에도 &lt;b&gt;퀘스트 정보&lt;/b&gt;, &lt;b&gt;대화 이벤트&lt;/b&gt;, &lt;b&gt;레이벨 디자인 데이터&lt;/b&gt; 등 다양한 게임 데이터를 Data Asset으로 활용할 수 있습니다. 예를 들어 퀘스트 보상, 조건, 스토리 텍스트 등을 Data Asset에 담아 두고 퀘스트 ID로 불러오거나, 지역별 환경 설정 값을 Data Asset으로 만들어 월드에 적용시키는 식입니다. Data Asset은 &lt;b&gt;개별 파일로 분리&lt;/b&gt;되어 있기 때문에 협업 시 충돌을 줄이고, 필요한 부분만 수정하여 콘텐츠 업데이트를 할 수도 있습니다. 또한 Data Table에 비해 &lt;b&gt;에디터 GUI를 통해 직관적으로 에셋과 다른 리소스를 연결&lt;/b&gt;할 수 있다는 장점이 있어, **데이터 간 참조(예: 아이템 Data Asset에 해당 아이템의 스태틱 메시 지정)**가 필요한 경우 선호됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;성능 및 최적화 고려사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 하드 참조 vs 소프트 참조&lt;/b&gt;: Data Asset이 &lt;b&gt;다른 에셋을 하드 참조&lt;/b&gt;하고 있으면 해당 Data Asset을 로드할 때 연관된 에셋(텍스처, 사운드 등)들도 모두 같이 로드됩니다. 이는 필요한 경우에는 편리하지만, 불필요한 리소스까지 메모리에 올라와 &lt;b&gt;로드 시간이 증가&lt;/b&gt;하거나 &lt;b&gt;메모리 낭비&lt;/b&gt;가 생길 수 있습니다 (&lt;a href=&quot;https://www.jooballin.com/p/unreal-engine-the-asset-manager-primary#:~:text=Whenever%20a%20data%20asset%20of,different%20assets%20to%20be%20loaded&quot;&gt;Unreal Engine - The Asset Manager, Primary Assets and Asset Bundles&lt;/a&gt;) (&lt;a href=&quot;https://www.jooballin.com/p/unreal-engine-the-asset-manager-primary#:~:text=Notice%20the%20property%20types%20of,their%20owning%20assets%20are%20loaded&quot;&gt;Unreal Engine - The Asset Manager, Primary Assets and Asset Bundles&lt;/a&gt;). Primary Data Asset을 설계할 때는 이러한 문제를 해결하기 위해 &lt;b&gt;TSoftObjectPtr&amp;lt;T&amp;gt;&lt;/b&gt; 등의 &lt;b&gt;소프트 참조&lt;/b&gt;를 사용하고, Asset Manager의 &lt;b&gt;Asset Bundle&lt;/b&gt; 기능을 활용해 맥락별로 로드할 자원을 구분하는 것이 좋습니다 (&lt;a href=&quot;https://www.jooballin.com/p/unreal-engine-the-asset-manager-primary#:~:text=Notice%20the%20property%20types%20of,their%20owning%20assets%20are%20loaded&quot;&gt;Unreal Engine - The Asset Manager, Primary Assets and Asset Bundles&lt;/a&gt;). 예를 들어 캐릭터 데이터에서 로비 화면에만 필요한 초상화 이미지는 &amp;ldquo;Lobby&amp;rdquo; 번들로, 게임플레이에 필요한 메쉬와 애니메이션은 &amp;ldquo;InGame&amp;rdquo; 번들로 태그 지정해두면, 로비 UI를 띄울 때는 초상화만 로드하고 게임 시작 시 나머지를 로드하는 최적화를 할 수 있습니다 (&lt;a href=&quot;https://www.jooballin.com/p/unreal-engine-the-asset-manager-primary#:~:text=Asset%20Bundles&quot;&gt;Unreal Engine - The Asset Manager, Primary Assets and Asset Bundles&lt;/a&gt;). 이처럼 &lt;b&gt;Soft Object/Class&lt;/b&gt; 참조 + Asset Bundle 조합은 대규모 프로젝트에서 필수적인 최적화 기법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Asset Manager를 통한 지연 로딩&lt;/b&gt;: Primary Data Asset으로 등록된 에셋들은 Asset Manager를 통해 &lt;b&gt;명시적으로 로드하기 전까지 메모리에 올라오지 않을 수&lt;/b&gt; 있습니다. 게임 시작 시 모든 것을 한꺼번에 불러오지 않고, 실제 필요한 시점에 LoadPrimaryAsset 등을 호출하여 로드하면 초기 메모리 사용량과 로딩 시간을 줄일 수 있습니다 (&lt;a href=&quot;https://dev.epicgames.com/documentation/en-us/unreal-engine/asset-management-in-unreal-engine#:~:text=Asset%20Management%20in%20Unreal%20Engine,Assets%20at%20the%20appropriate%20time&quot;&gt;Asset Management in Unreal Engine - Epic Games Developers&lt;/a&gt;). 또한 LoadPrimaryAssets나 AsyncLoadPrimaryAssetList를 사용하면 다수의 에셋을 비동기로 불러올 수 있으므로, 게임 플레이 중 프레임 드랍을 유발하지 않고 데이터를 준비할 수 있습니다. 반면 일반 Data Asset을 다른 객체가 &lt;b&gt;하드 참조&lt;/b&gt;하고 있으면 게임 패키징 시 항상 포함되고, 해당 객체가 로드될 때 함께 메모리에 올라오므로 이 부분을 염두에 두어야 합니다. &lt;b&gt;요약&lt;/b&gt;: 참조 수가 많거나 용량이 큰 에셋 묶음은 Primary Data Asset으로 관리해 &lt;b&gt;필요할 때만 로드&lt;/b&gt;하고, 소규모이거나 항상 필요한 데이터는 Data Asset으로 두어 &lt;b&gt;간편하게 접근&lt;/b&gt;하는 식으로 전략을 세울 수 있습니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1e1wq3n/primary_data_assets_only_have_exactly_1_instance/#:~:text=typically%20used%20for%20containing%20large,until%20you%20manually%20load%20them&quot;&gt;Primary Data assets only have exactly 1 instance stored in memory, right? : r/unrealengine&lt;/a&gt;).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Data Asset 다량 사용 시 고려사항&lt;/b&gt;: 프로젝트에 수백 개 이상의 Data Asset이 존재해도 큰 문제는 없지만, 너무 세분화된 에셋은 오히려 관리 비용을 늘릴 수 있습니다. Asset Manager는 지정된 폴더를 스캔하여 Primary Data Asset 목록을 유지하므로, &lt;b&gt;Primary Asset의 유형이 지나치게 많으면 초기 스캔 비용&lt;/b&gt;이 증가할 수 있습니다. 그러나 이 비용은 한 번이고, 검색 결과 역시 경량화되어 관리되므로 일반적으로 감내할 만합니다. 또한 Primary Asset ID의 &lt;b&gt;이름(Name)&lt;/b&gt; 부분은 기본적으로 에셋 이름으로 설정되기 때문에, 에셋을 &lt;b&gt;리네임하면 ID가 바뀐다는 점&lt;/b&gt;도 유의해야 합니다 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,%ED%83%AD%EC%97%90%EC%84%9C%20%EC%B0%B8%EC%A1%B0%ED%95%98%EA%B3%A0%EC%9E%90%20%ED%95%98%EB%8A%94%20Primary%20Asset%EC%9D%98&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;). Asset Manager를 사용할 때는 가능하면 에셋 이름을 변경하지 않거나, 변경 시 관련 코드를 업데이트해야 합니다. (필요하다면 GetPrimaryAssetId 오버라이드 시 Primary Asset Name을 별도 변수로 반환하여 &lt;b&gt;이름과 무관한 ID&lt;/b&gt;를 가지도록 할 수도 있습니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 블루프린트 vs C++ 데이터 에셋&lt;/b&gt;: Data Asset을 블루프린트 클래스로 만들어 사용할 수도 있고 (앞서 언급한 Data-Only Blueprint), C++ 클래스의 인스턴스로 사용할 수도 있습니다. &lt;b&gt;메모리 및 성능 측면&lt;/b&gt;에서는 블루프린트 클래스는 UClass 자체로 약간의 오버헤드가 있고 GC에 추가되는 등 부하가 있으며, C++ Data Asset 인스턴스는 보다 가볍습니다 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=Blueprint%20%EC%9D%B4%EC%99%B8%EC%9D%98%20Asset&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;). 따라서 단순한 데이터의 경우 &lt;b&gt;데이터 전용 블루프린트보다는 C++ 기반 Data Asset 인스턴스&lt;/b&gt; 형태가 유리할 수 있습니다. 반대로 디자이너가 빈번히 수정해야 하고 복잡한 구조의 데이터라면 블루프린트 Data Asset으로 만들어 편집 편의를 높일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. Data Table과의 비교&lt;/b&gt;: Data Asset과 Data Table(데이터 테이블)은 모두 구조화된 게임 데이터를 저장하는 방법입니다. &lt;b&gt;Data Table&lt;/b&gt;은 하나의 파일에 여러 행(row)의 데이터를 가지고 있어 &lt;b&gt;한꺼번에 일괄 편집&lt;/b&gt;하거나 CSV로 입출력하기 편리하지만, 개별 행을 찾는 데 인덱스나 이름으로 조회하는 과정이 필요합니다. 블루프린트에서 Data Table의 특정 행을 가져오려면 &lt;b&gt;데이터 테이블을 순회하거나 RowName으로 찾는 비용&lt;/b&gt;이 들 수 있는데, 복잡한 테이블일수록 이 오버헤드가 커질 수 있습니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1e5uwr4/data_asset_workflows_and_pitfalls/#:~:text=,to%20it%27s%20primary%20data%20asset&quot;&gt;Data Asset Workflows and Pitfalls : r/unrealengine&lt;/a&gt;). 반면 &lt;b&gt;Data Asset&lt;/b&gt;은 필요한 데이터만 개별 객체로 &lt;b&gt;직접 참조&lt;/b&gt;할 수 있으므로, 코드나 블루프린트에서 곧바로 프로퍼티에 접근할 수 있어 접근 비용이 낮습니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1e5uwr4/data_asset_workflows_and_pitfalls/#:~:text=,to%20it%27s%20primary%20data%20asset&quot;&gt;Data Asset Workflows and Pitfalls : r/unrealengine&lt;/a&gt;). 또한 Data Asset은 그 안에 다른 에셋 레퍼런스를 바로 물고 있을 수 있어 &lt;b&gt;에디터 상에서 에셋 간 참조를 연결&lt;/b&gt;하기 쉬운 장점이 있습니다. 한편으로 Data Table은 엑셀 같은 형태로 대량의 데이터를 관리하기 좋고, &lt;b&gt;Data Asset은 객체 지향적으로 데이터 구조를 관리&lt;/b&gt;하기 좋습니다. 실제 개발에서는 &lt;b&gt;소량의 복잡한 데이터&lt;/b&gt;는 Data Asset으로, &lt;b&gt;대량의 단순 테이블 데이터&lt;/b&gt;는 Data Table로 쓰는 식으로 병행하기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. 실행 중 데이터 수정 여부&lt;/b&gt;: Data Asset(Primary 포함)은 기본적으로 &lt;b&gt;런타임에 데이터를 수정하지 않고 읽기 전용&lt;/b&gt;으로 사용하는 것이 권장됩니다. 일반적으로 이러한 에셋의 UPROPERTY들은 EditDefaultsOnly, BlueprintReadOnly 등으로 선언하여 &lt;b&gt;게임 실행 중에는 값을 바꾸지 못하게&lt;/b&gt; 설계하며 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1e1wq3n/primary_data_assets_only_have_exactly_1_instance/#:~:text=,only%20default%20data%20%2B%20functions&quot;&gt;Primary Data assets only have exactly 1 instance stored in memory, right? : r/unrealengine&lt;/a&gt;), 오직 에디터에서 설정한 기본값을 참조하는 용도로 씁니다. 실제로 언리얼에서 Data Asset 객체는 게임이 시작하면 로드된 상태로 메모리에 존재하고, 별도로 New해서 인스턴스를 만드는 일이 거의 없습니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1e1wq3n/primary_data_assets_only_have_exactly_1_instance/#:~:text=As%20I%20mentioned%20above%2C%20UPrimaryDataAsset,of%20the%20object%20you%20constructed&quot;&gt;Primary Data assets only have exactly 1 instance stored in memory, right? : r/unrealengine&lt;/a&gt;). 만약 런타임에 값이 변경되어야 하는 &lt;b&gt;가변적인 데이터&lt;/b&gt;라면 Data Asset보다는 세이브 게임 객체나 별도의 구조체/클래스를 사용하고, Data Asset은 &lt;b&gt;초기 설정값 제공&lt;/b&gt;만 담당하는 편이 좋습니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1e1wq3n/primary_data_assets_only_have_exactly_1_instance/#:~:text=,only%20default%20data%20%2B%20functions&quot;&gt;Primary Data assets only have exactly 1 instance stored in memory, right? : r/unrealengine&lt;/a&gt;). 예를 들어 인게임에서 획득한 아이템의 내구도나 퀘스트 진행도 같은 값은 Data Asset이 아니라 별도 컴포넌트나 저장 시스템을 통해 관리해야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;블루프린트 및 C++에서의 사용 방식&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;블루프린트에서 Data Asset 접근&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블루프린트에서는 Data Asset을 &lt;b&gt;변수로 참조하거나 함수 입력으로 받아 사용하는 방식&lt;/b&gt;으로 활용합니다. 예를 들어 캐릭터 블루프린트에 무기 데이터라는 변수를 만들고 타입을 WeaponDataAsset 클래스로 지정하면, 에디터에서 해당 변수에 특정 무기 Data Asset을 할당할 수 있습니다. 그런 다음 플레이 중에 그 변수에서 무기 속성 값을 끌어와 사용하면 됩니다 (예: 무기데이터.공격력 형태로 접근). Data Asset 클래스에 &lt;b&gt;BlueprintReadOnly&lt;/b&gt;로 공개된 프로퍼티들은 이렇게 블루프린트에서 직접 읽어 쓸 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Primary Data Asset을 블루프린트로 다룰 때는 &lt;b&gt;Asset Manager의 노드&lt;/b&gt;들을 활용하면 편리합니다. 대표적으로 &lt;b&gt;Get Primary Asset Id List&lt;/b&gt; 노드는 지정한 Primary Asset Type에 해당하는 모든 에셋의 ID 목록을 반환해 줍니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/setup-primary-assets-via-blueprint-for-asset-manager-scan/559269#:~:text=Now%20in%20BP%20you%20can,use%20functionality%20such%20as&quot;&gt;Setup primary assets via blueprint for asset manager scan - Blueprint - Epic Developer Community Forums&lt;/a&gt;). 이를 사용하여, 예를 들어 Primary Asset Type &quot;Item&quot;에 속한 모든 아이템의 ID 리스트를 얻을 수 있습니다. 그 다음 &lt;b&gt;Async Load Primary Asset List&lt;/b&gt; 노드를 사용하면 얻은 ID 목록의 에셋들을 &lt;b&gt;비동기로 한꺼번에 로드&lt;/b&gt;할 수 있습니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/setup-primary-assets-via-blueprint-for-asset-manager-scan/559269#:~:text=Now%20in%20BP%20you%20can,use%20functionality%20such%20as&quot;&gt;Setup primary assets via blueprint for asset manager scan - Blueprint - Epic Developer Community Forums&lt;/a&gt;). 개별 Primary Data Asset 하나만 로드하려면 &lt;b&gt;Async Load Primary Asset&lt;/b&gt; 노드를 사용할 수 있으며, 이 노드는 완료 시 로드된 에셋을 반환하거나 delegate로 로드 완료 시점을 알려줍니다. 블루프린트에서 Asset Manager 노드를 활용하면 프로그래머의 도움 없이도 디자이너가 필요한 데이터 에셋을 찾아 쓰거나, 여러 에셋을 &lt;b&gt;동적으로 불러오는 로직&lt;/b&gt;을 구성할 수 있습니다. (참고로 Primary Asset이 블루프린트 클래스인 경우에는 &lt;b&gt;Async Load Primary Asset Class&lt;/b&gt; 노드를 사용할 수도 있습니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/setup-primary-assets-via-blueprint-for-asset-manager-scan/559269#:~:text=,UE%20%2F%20your%20own%20logic&quot;&gt;Setup primary assets via blueprint for asset manager scan - Blueprint - Epic Developer Community Forums&lt;/a&gt;).)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: 인벤토리 시스템에서 Primary Data Asset으로 등록된 모든 아이템을 게임 시작 시 로드하고 싶다면, BeginPlay에서 Get Primary Asset Id List (Item) &amp;rarr; Async Load Primary Asset List를 호출하여 아이템 데이터를 모두 메모리에 올려둘 수 있습니다. 반대로 필요한 순간에만 로드하고자 하면 해당 순간에 Async Load Primary Asset으로 특정 ID의 아이템만 불러오도록 구현하면 됩니다. 블루프린트에서 얻은 Primary Data Asset 참조는 Data Asset과 마찬가지로 &lt;b&gt;그 안의 프로퍼티에 접근&lt;/b&gt;하여 사용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C++ 코드에서의 활용 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서는 Data Asset을 직접 로드하거나 UPROPERTY로 참조하여 사용할 수 있습니다. &lt;b&gt;정적 참조&lt;/b&gt;의 경우, 다른 UObject 클래스 내에 UPROPERTY로 Data Asset 포인터를 선언하고 에디터에서 할당하는 방법이 있습니다. 예를 들어 캐릭터 C++ 클래스에 다음과 같이 선언하면:&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=&quot;Item&quot;)
UItemDataAsset* EquippedItemData;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에디터에서 해당 캐릭터의 기본값에 EquippedItemData로 원하는 아이템 Data Asset을 지정할 수 있습니다. 플레이 중에는 이 포인터가 가리키는 Data Asset 객체가 이미 로드되어 있으므로 (UObject hard reference), 바로 EquippedItemData-&amp;gt;Damage 등의 식으로 데이터에 접근하면 됩니다. 이 방식은 &lt;b&gt;직접 참조&lt;/b&gt;이므로 코드를 간단하게 해주지만, 해당 Data Asset이 항상 메모리에 상주하게 됨을 유념해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동적 로드&lt;/b&gt;나 &lt;b&gt;지연 로드&lt;/b&gt;를 원한다면 C++에서 &lt;b&gt;Asset Manager API&lt;/b&gt;를 사용할 수 있습니다. UAssetManager::Get()를 통해 싱글톤 인스턴스를 얻은 뒤 여러 함수를 활용할 수 있습니다. 예를 들어 Primary Asset Type이 &quot;Item&quot;인 자산들을 모두 불러오려면:&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;TArray&amp;lt;FPrimaryAssetId&amp;gt; ItemAssetIds;
UAssetManager::Get().GetPrimaryAssetIdList(FPrimaryAssetType(&quot;Item&quot;), ItemAssetIds);

// 첫 번째 아이템을 동기로 로드하는 예시
if(ItemAssetIds.Num() &amp;gt; 0)
{
    FPrimaryAssetId ItemId = ItemAssetIds[0];
    // 동기 로드 (비추천: 게임 실행 중에는 가능하면 비동기 사용)
    UItemDataAsset* ItemData = Cast&amp;lt;UItemDataAsset&amp;gt;(UAssetManager::Get().LoadPrimaryAsset(ItemId));
    if(ItemData)
    {
        UE_LOG(LogTemp, Log, TEXT(&quot;Loaded Item: %s, Damage = %.1f&quot;), *ItemData-&amp;gt;ItemName.ToString(), ItemData-&amp;gt;Damage);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드는 Asset Manager에 등록된 &quot;Item&quot; 타입의 ID들을 가져와, 그 중 첫 번째를 &lt;b&gt;동기 로드&lt;/b&gt;하는 예시입니다. LoadPrimaryAsset는 내부적으로 해당 에셋을 찾고 로드한 뒤, 로드된 객체를 반환합니다. 이 함수는 즉시 로드하므로 게임 중에는 사용에 주의해야 하지만, 미리 로딩해두거나 에디터 유틸리티 등에서는 유용할 수 있습니다 (&lt;a href=&quot;https://dev.epicgames.com/documentation/en-us/unreal-engine/asset-management-in-unreal-engine#:~:text=Asset%20Management%20in%20Unreal%20Engine,Assets%20at%20the%20appropriate%20time&quot;&gt;Asset Management in Unreal Engine - Epic Games Developers&lt;/a&gt;). 실제 게임 플레이 중에는 &lt;b&gt;비동기 로드&lt;/b&gt;를 선호하며, 이를 위해 LoadPrimaryAsset(...)에 FStreamableDelegate를 전달하거나, LoadPrimaryAssets (여러 개 한꺼번에) 함수를 사용할 수 있습니다 (&lt;a href=&quot;https://dev.epicgames.com/documentation/en-us/unreal-engine/asset-management-in-unreal-engine#:~:text=Asset%20Management%20in%20Unreal%20Engine,Assets%20at%20the%20appropriate%20time&quot;&gt;Asset Management in Unreal Engine - Epic Games Developers&lt;/a&gt;). 또는 UAssetManager::GetStreamableManager().RequestAsyncLoad(FPrimaryAssetId, ...) 형태로 Streamable Manager를 직접 이용하는 것도 가능합니다. 비동기 로드를 사용하면 로드 완료 시점을 콜백으로 받아 처리할 수 있으므로, 예를 들어 아이템을 획득하는 순간 해당 아이템 Data Asset을 로드하고, 완료되면 UI를 업데이트하는 흐름을 만들 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 C++에서는 Primary Data Asset이 블루프린트 클래스인 경우 LoadPrimaryAssetClass를 통해 UClass 자체를 로드한 뒤 GetDefaultObject()로 기본 데이터를 읽는 식의 활용도 가능합니다 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,%EB%B0%9B%EC%95%84%20BP_MyRectangle%EB%9D%BC%EB%8A%94%20BP%20Class%EB%A5%BC%20%EC%83%9D%EC%84%B1&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;). 그러나 일반적으로 Data Asset은 인스턴스로 존재하므로 위 예시처럼 객체를 직접 다루는 편이 간편합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  Tip:&lt;/b&gt; UAssetManager에는 GetPrimaryAssetObject, GetPrimaryAssetPath 같은 유용한 함수들도 있습니다. Primary Asset ID만 알고 있을 때, 이미 로드된 객체를 얻고 싶으면 GetPrimaryAssetObject를 쓰고, 경로나 클래스 등 메타정보가 필요하면 관련 함수를 사용할 수 있습니다. 또한 Asset Manager가 관리하지 않는 일반 에셋은 UAssetManager::GetStreamableManager()를 통해 FSoftObjectPath로 로드할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추가로 알아두면 좋은 정보&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Data Asset은 객체 인스턴스&lt;/b&gt;: 앞서 설명했듯이 Data Asset으로 생성된 .uasset 파일은 해당 클래스의 객체 하나가 저장된 것입니다. 그래서 블루프린트 클래스로 만든 에셋과 달리 &lt;b&gt;상속을 통해 파생 클래스를 만들 수는 없고&lt;/b&gt; (이미 인스턴스이므로 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,Asset%EC%9D%98%20Class%EC%97%90%20Access%ED%95%98%EB%8A%94%20%EB%B0%A9%EB%B2%95&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;)】, &lt;b&gt;필요 시 데이터를 담은 채 복제(duplicate)하여 새 에셋을 만드는 방식&lt;/b&gt;으로 확장합니다. 예를 들어 SwordDataAsset을 복제하여 AxeDataAsset을 만들고 수치만 변경하는 식입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Unity ScriptableObject와 유사&lt;/b&gt;: Unreal의 Data Asset 개념은 Unity의 ScriptableObject와 매우 유사합니다. 실제 Unity 경험이 있는 개발자는 Data Asset을 보면 &quot;에디터에서 사용하는 ScriptableObject 같은 것&quot;이라고 이해할 수 있습니 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/15jpvlc/dataassets_are_incredibly_useful/#:~:text=%E2%80%A2%20%E2%80%A2%20Edited&quot;&gt;DataAssets are incredibly useful : r/unrealengine&lt;/a&gt;)】. 둘 다 &lt;b&gt;직렬화된 오브젝트를 에셋 자원으로 저장&lt;/b&gt;하고, 에디터에서 편집하며, 게임에서는 해당 오브젝트의 데이터를 읽는 용도로 쓰입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Data Asset vs CSV/JSON&lt;/b&gt;: Data Asset을 사용하면 언리얼 에디터 내에서 데이터 편집이 가능하고 다른 에셋과의 연결도 쉬워집니다. 반면 외부 툴로 작성된 데이터(엑셀, JSON 등)를 가져오려면 Data Table을 통하거나 임포터를 작성해야 합니다. 소량의 데이터는 Data Asset으로 직접 입력해도 무방하지만, &lt;b&gt;방대한 양의 레코드&lt;/b&gt;를 관리해야 한다면 외부에서 작성해 Data Table로 들여오는 편이 효율적일 수 있습니다. 상황에 따라 혼용을 고려하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PrimaryAssetLabel 활용&lt;/b&gt;: 언리얼엔진에는 Primary Data Asset 이외에도 &lt;b&gt;Primary Asset Label&lt;/b&gt;이라는 것이 있습니다. 이는 여러 자산들을 그룹으로 묶어 별도의 Primary Asset처럼 다루게 해주는 에셋입니다. PrimaryAssetLabel을 사용하면 서로 다른 종류의 에셋(예: 특정 레벨과 거기에 등장하는 몇몇 프랍 프리팹)을 하나의 Primary Asset ID로 묶어 관리할 수도 있습니다. 복잡한 에셋 로드 시나리오에서는 PrimaryAssetLabel도 고려해볼 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디버깅 Asset Manager&lt;/b&gt;: AssetManager 설정이 제대로 되었는지 확인하려면, 에디터 실행 시 Output Log에 Asset Manager 로드 관련 로그를 활성화하거나, GetPrimaryAssetIdList 결과를 출력해 보면 됩니다. 또는 Unreal Insights의 Asset Load 트레이스를 활용해 어떤 Primary Asset들이 언제 로드/언로드 되는지 추적할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;以上와 같이, Data Asset과 Primary Data Asset은 언리얼 엔진에서 데이터 주도 설계(Data-Driven Design)를 구현하는 데 핵심적인 도구입니다. 작은 설정부터 대규모 컨텐츠 관리까지 폭넓게 활용할 수 있으므로, 자신의 프로젝트 규모와 필요에 맞게 두 방식을 적절히 조합하시기 바랍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고 자료:&lt;/b&gt; Unreal Engine 공식 문서 및 개발자 커뮤니티의 조언을 기반으로 정리 (UDataAsset/UPrimaryDataAsset API, Asset Manager 가이드 등 (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,Asset%EC%9C%BC%EB%A1%9C%20%EB%A7%8C%EB%93%9C%EB%A0%A4%EB%A9%B4%20GetPrimaryAssetId%20%ED%95%A8%EC%88%98%EB%A5%BC%20override%ED%95%98%EC%97%AC&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;) (&lt;a href=&quot;https://redchiken.tistory.com/358#:~:text=,%EC%97%90%EC%84%9C%20%EB%B0%98%ED%99%98%ED%95%98%EB%8A%94%20%EA%B0%92%EA%B3%BC%20%EA%B0%99%EC%95%84%EC%95%BC%20%ED%95%9C%EB%8B%A4&quot;&gt;[Architecture] DataAsset과 Asset Manager :: 치킨 날다&lt;/a&gt;) (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/15jpvlc/dataassets_are_incredibly_useful/#:~:text=Data%20assets%20is%20an%20uboject,single%20entry%20in%20data%20table&quot;&gt;DataAssets are incredibly useful : r/unrealengine&lt;/a&gt;) (&lt;a href=&quot;https://forums.unrealengine.com/t/setup-primary-assets-via-blueprint-for-asset-manager-scan/559269#:~:text=Now%20in%20BP%20you%20can,use%20functionality%20such%20as&quot;&gt;Setup primary assets via blueprint for asset manager scan - Blueprint - Epic Developer Community Forums&lt;/a&gt;)】.&lt;/p&gt;</description>
      <category>UnraealEngine</category>
      <author>남찬우</author>
      <guid isPermaLink="true">https://kakaoncw.tistory.com/97</guid>
      <comments>https://kakaoncw.tistory.com/97#entry97comment</comments>
      <pubDate>Wed, 26 Mar 2025 13:23:05 +0900</pubDate>
    </item>
    <item>
      <title>AbilitySystem</title>
      <link>https://kakaoncw.tistory.com/96</link>
      <description>&lt;h1&gt;API 가이드: AbilitySystemComponent의 Ability 관련 기능&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진의 AbilitySystemComponent는 게임플레이 어빌리티 시스템(Gameplay Ability System, GAS)의 핵심 구성 요소입니다. 게임플레이 어빌리티 시스템은 액터에게 특별한 능력, 스킬, 효과 등을 부여하고 관리하기 위한 언리얼 엔진의 프레임워크입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 가이드에서는 특히 AbilitySystemComponent의 어빌리티(Ability) 관련 기능과 API에 초점을 맞춥니다. 주요 내용은 다음과 같습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;어빌리티 부여 및 활성화 관리&lt;/li&gt;
&lt;li&gt;어빌리티 스펙(Spec) 구성 및 처리&lt;/li&gt;
&lt;li&gt;어빌리티 활성화와 네트워크 복제 연동&lt;/li&gt;
&lt;li&gt;입력 시스템과 어빌리티 연결&lt;/li&gt;
&lt;li&gt;어빌리티 태그 기반 관리 및 필터링&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AbilitySystemComponent는 다양한 게임 장르에서 캐릭터 능력, 스킬, 공격, 버프, 디버프 등을 모듈화하고 확장 가능한 방식으로 구현할 수 있게 해주는 강력한 도구입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클래스/함수별 상세 설명&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AbilitySystemComponent 클래스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UAbilitySystemComponent는 UGameplayTasksComponent를 상속받고, IGameplayTagAssetInterface와 IAbilitySystemReplicationProxyInterface 인터페이스를 구현하는 컴포넌트입니다. 이 컴포넌트는 액터에게 어빌리티 시스템을 제공하며, 어빌리티 관리, 게임플레이 이펙트, 어트리뷰트 등을 처리합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;UCLASS(ClassGroup=AbilitySystem, hidecategories=(Object,LOD,Lighting,Transform,Sockets,TextureStreaming), editinlinenew, meta=(BlueprintSpawnableComponent))
class GAMEPLAYABILITIES_API UAbilitySystemComponent : public UGameplayTasksComponent, public IGameplayTagAssetInterface, public IAbilitySystemReplicationProxyInterface
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어빌리티 부여 및 관리 함수&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;어빌리티 부여 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GiveAbility&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;FGameplayAbilitySpecHandle GiveAbility(const FGameplayAbilitySpec&amp;amp; AbilitySpec);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 어빌리티를 부여합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilitySpec: 부여할 어빌리티의 스펙 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 어빌리티 스펙 핸들(활성화에 사용)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 이 함수는 새로운 어빌리티를 시스템에 추가합니다. 액터가 권한(Authority)을 가진 경우에만 작동합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K2_GiveAbility (블루프린트 호출 가능)&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = &quot;Gameplay Abilities&quot;, meta = (DisplayName = &quot;Give Ability&quot;, ScriptName = &quot;GiveAbility&quot;))
FGameplayAbilitySpecHandle K2_GiveAbility(TSubclassOf&amp;lt;UGameplayAbility&amp;gt; AbilityClass, int32 Level = 0, int32 InputID = -1);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 블루프린트에서 어빌리티를 부여하기 위한 함수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilityClass: 부여할 어빌리티 클래스&lt;/li&gt;
&lt;li&gt;Level: 어빌리티 레벨 (기본값: 0)&lt;/li&gt;
&lt;li&gt;InputID: 어빌리티 활성화에 사용할 입력 ID (기본값: -1, 입력 바인딩 없음)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 어빌리티 스펙 핸들&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 클래스의 어빌리티를 지정된 레벨로 생성하고 시스템에 추가합니다. BlueprintAuthorityOnly 태그는 이 함수가 서버에서만 호출될 수 있음을 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GiveAbilityAndActivateOnce&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;FGameplayAbilitySpecHandle GiveAbilityAndActivateOnce(FGameplayAbilitySpec&amp;amp; Spec, const FGameplayEventData* GameplayEventData = nullptr);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 어빌리티를 부여하고 즉시 한 번만 활성화합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spec: 부여 및 활성화할 어빌리티 스펙&lt;/li&gt;
&lt;li&gt;GameplayEventData: 활성화에 사용할 이벤트 데이터 (선택 사항)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 어빌리티 스펙 핸들&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 어빌리티를 부여하고 즉시 활성화한 후, 활성화가 끝나면 자동으로 제거합니다. 서버에서만 작동하며, 로컬 또는 로컬 예측 실행 정책을 가진 어빌리티에는 사용할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K2_GiveAbilityAndActivateOnce&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = &quot;Gameplay Abilities&quot;, meta = (DisplayName = &quot;Give Ability And Activate Once&quot;, ScriptName = &quot;GiveAbilityAndActivateOnce&quot;))
FGameplayAbilitySpecHandle K2_GiveAbilityAndActivateOnce(TSubclassOf&amp;lt;UGameplayAbility&amp;gt; AbilityClass, int32 Level = 0, int32 InputID = -1);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 블루프린트에서 어빌리티를 부여하고 즉시 한 번만 활성화하기 위한 함수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작동 방식&lt;/b&gt;: GiveAbilityAndActivateOnce의 블루프린트 호출 가능 버전입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;어빌리티 제거 함수&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ClearAbility&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = &quot;Gameplay Abilities&quot;)
void ClearAbility(const FGameplayAbilitySpecHandle&amp;amp; Handle);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 어빌리티를 제거합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Handle: 제거할 어빌리티의 핸들&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 핸들을 가진 어빌리티를 시스템에서 제거합니다. 서버에서만 작동합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ClearAllAbilities&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category=&quot;Gameplay Abilities&quot;)
void ClearAllAbilities();
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 모든 어빌리티를 제거합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 이 함수는 모든 어빌리티를 시스템에서 제거합니다. 서버에서만 작동합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ClearAllAbilitiesWithInputID&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = &quot;Gameplay Abilities&quot;)
void ClearAllAbilitiesWithInputID(int32 InputID = 0);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 입력 ID에 바인딩된 모든 어빌리티를 제거합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InputID: 제거할 어빌리티의 입력 ID (기본값: 0)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 입력 ID를 가진 모든 어빌리티를 시스템에서 제거합니다. 서버에서만 작동합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SetRemoveAbilityOnEnd&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void SetRemoveAbilityOnEnd(FGameplayAbilitySpecHandle AbilitySpecHandle);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 어빌리티가 끝난 후 자동으로 제거되도록 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilitySpecHandle: 설정할 어빌리티의 핸들&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 어빌리티가 활성화된 상태라면 종료 시 제거하도록 표시하고, 그렇지 않다면 즉시 제거합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어빌리티 활성화 함수&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TryActivateAbility&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, Category = &quot;Abilities&quot;)
bool TryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool bAllowRemoteActivation = true);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 어빌리티를 활성화하려고 시도합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilityToActivate: 활성화할 어빌리티의 핸들&lt;/li&gt;
&lt;li&gt;bAllowRemoteActivation: 원격 활성화 허용 여부 (기본값: true)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 활성화 시도 성공 여부 (나중에 실패할 수도 있음)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 어빌리티를 활성화하려고 시도합니다. 비용과 요구사항을 확인합니다. 원격 활성화가 허용되면 클라이언트에서 서버 어빌리티를 활성화할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TryActivateAbilityByClass&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, Category = &quot;Abilities&quot;)
bool TryActivateAbilityByClass(TSubclassOf&amp;lt;UGameplayAbility&amp;gt; InAbilityToActivate, bool bAllowRemoteActivation = true);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 지정된 클래스의 어빌리티를 활성화하려고 시도합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InAbilityToActivate: 활성화할 어빌리티 클래스&lt;/li&gt;
&lt;li&gt;bAllowRemoteActivation: 원격 활성화 허용 여부 (기본값: true)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 활성화 시도 성공 여부&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 클래스의 어빌리티를 찾아 활성화하려고 시도합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TryActivateAbilitiesByTag&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, Category = &quot;Abilities&quot;)
bool TryActivateAbilitiesByTag(const FGameplayTagContainer&amp;amp; GameplayTagContainer, bool bAllowRemoteActivation = true);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 지정된 태그를 가진 모든 어빌리티를 활성화하려고 시도합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GameplayTagContainer: 필요한 태그 컨테이너&lt;/li&gt;
&lt;li&gt;bAllowRemoteActivation: 원격 활성화 허용 여부 (기본값: true)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 활성화된 어빌리티가 하나라도 있는지 여부&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 태그와 일치하는 모든 어빌리티를 찾아 활성화를 시도합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;InternalTryActivateAbility&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;bool InternalTryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, FPredictionKey InPredictionKey = FPredictionKey(), UGameplayAbility ** OutInstancedAbility = nullptr, FOnGameplayAbilityEnded::FDelegate* OnGameplayAbilityEndedDelegate = nullptr, const FGameplayEventData* TriggerEventData = nullptr);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 어빌리티 활성화 과정의 내부 구현입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilityToActivate: 활성화할 어빌리티의 핸들&lt;/li&gt;
&lt;li&gt;InPredictionKey: 예측 키 (선택 사항)&lt;/li&gt;
&lt;li&gt;OutInstancedAbility: 인스턴스화된 어빌리티 출력 포인터 (선택 사항)&lt;/li&gt;
&lt;li&gt;OnGameplayAbilityEndedDelegate: 어빌리티 종료 시 호출할 대리자 (선택 사항)&lt;/li&gt;
&lt;li&gt;TriggerEventData: 이벤트 데이터 (선택 사항)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 활성화 성공 여부&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 이 함수는 어빌리티 활성화의 실제 로직을 처리합니다. 비용 확인, 태그 요구사항 확인, 인스턴스화 등을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어빌리티 검색 및 조회 함수&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;GetAllAbilities&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, BlueprintPure = false, Category = &quot;Gameplay Abilities&quot;)
void GetAllAbilities(TArray&amp;lt;FGameplayAbilitySpecHandle&amp;gt;&amp;amp; OutAbilityHandles) const;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 모든 부여된 어빌리티의 핸들을 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OutAbilityHandles: 결과 핸들을 저장할 배열&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 현재 활성화 가능한 모든 어빌리티의 핸들을 출력 배열에 채웁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FindAllAbilitiesWithTags&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, BlueprintPure = false, Category = &quot;Gameplay Abilities&quot;)
void FindAllAbilitiesWithTags(TArray&amp;lt;FGameplayAbilitySpecHandle&amp;gt;&amp;amp; OutAbilityHandles, FGameplayTagContainer Tags, bool bExactMatch = true) const;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 태그를 가진 모든 어빌리티를 찾습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OutAbilityHandles: 결과 핸들을 저장할 배열&lt;/li&gt;
&lt;li&gt;Tags: 찾을 태그&lt;/li&gt;
&lt;li&gt;bExactMatch: 정확히 일치해야 하는지 여부 (기본값: true)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 태그와 일치하는 모든 어빌리티를 찾아 출력 배열에 채웁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FindAllAbilitiesMatchingQuery&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, BlueprintPure = false, Category = &quot;Gameplay Abilities&quot;)
void FindAllAbilitiesMatchingQuery(TArray&amp;lt;FGameplayAbilitySpecHandle&amp;gt;&amp;amp; OutAbilityHandles, FGameplayTagQuery Query) const;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 태그 쿼리와 일치하는 모든 어빌리티를 찾습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OutAbilityHandles: 결과 핸들을 저장할 배열&lt;/li&gt;
&lt;li&gt;Query: 게임플레이 태그 쿼리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 태그 쿼리와 일치하는 모든 어빌리티를 찾아 출력 배열에 채웁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FindAllAbilitiesWithInputID&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, BlueprintPure = false, Category = &quot;Gameplay Abilities&quot;)
void FindAllAbilitiesWithInputID(TArray&amp;lt;FGameplayAbilitySpecHandle&amp;gt;&amp;amp; OutAbilityHandles, int32 InputID = 0) const;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 입력 ID를 가진 모든 어빌리티를 찾습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OutAbilityHandles: 결과 핸들을 저장할 배열&lt;/li&gt;
&lt;li&gt;InputID: 찾을 입력 ID (기본값: 0)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 입력 ID에 바인딩된 모든 어빌리티를 찾아 출력 배열에 채웁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FindAbilitySpecFromHandle&lt;/h4&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;FGameplayAbilitySpec* FindAbilitySpecFromHandle(FGameplayAbilitySpecHandle Handle, EConsiderPending ConsiderPending = EConsiderPending::PendingRemove) const;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 핸들을 가진 어빌리티 스펙을 찾습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Handle: 찾을 어빌리티 핸들&lt;/li&gt;
&lt;li&gt;ConsiderPending: 대기 중인 어빌리티를 고려할지 여부&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 찾은 어빌리티 스펙 포인터 (찾지 못한 경우 nullptr)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 핸들과 일치하는 어빌리티 스펙을 찾습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FindAbilitySpecFromClass&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;FGameplayAbilitySpec* FindAbilitySpecFromClass(TSubclassOf&amp;lt;UGameplayAbility&amp;gt; InAbilityClass) const;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 클래스의 어빌리티 스펙을 찾습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InAbilityClass: 찾을 어빌리티 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 찾은 어빌리티 스펙 포인터 (찾지 못한 경우 nullptr)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 클래스의 어빌리티 스펙을 찾습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FindAbilitySpecFromInputID&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;FGameplayAbilitySpec* FindAbilitySpecFromInputID(int32 InputID) const;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 입력 ID를 가진 어빌리티 스펙을 찾습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InputID: 찾을 입력 ID&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 찾은 어빌리티 스펙 포인터 (찾지 못한 경우 nullptr)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 입력 ID에 바인딩된 어빌리티 스펙을 찾습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어빌리티 취소 함수&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CancelAbility&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void CancelAbility(UGameplayAbility* Ability);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 어빌리티를 취소합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ability: 취소할 어빌리티&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 어빌리티를 취소합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CancelAbilityHandle&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void CancelAbilityHandle(const FGameplayAbilitySpecHandle&amp;amp; AbilityHandle);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 핸들을 가진 어빌리티를 취소합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilityHandle: 취소할 어빌리티의 핸들&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 핸들을 가진 어빌리티를 취소합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CancelAbilities&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void CancelAbilities(const FGameplayTagContainer* WithTags=nullptr, const FGameplayTagContainer* WithoutTags=nullptr, UGameplayAbility* Ignore=nullptr);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 태그 조건을 만족하는 어빌리티를 취소합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WithTags: 포함해야 하는 태그 (선택 사항)&lt;/li&gt;
&lt;li&gt;WithoutTags: 포함하지 않아야 하는 태그 (선택 사항)&lt;/li&gt;
&lt;li&gt;Ignore: 무시할 어빌리티 (선택 사항)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 태그 조건을 만족하는 모든 어빌리티를 취소합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CancelAllAbilities&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void CancelAllAbilities(UGameplayAbility* Ignore=nullptr);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 모든 어빌리티를 취소합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ignore: 무시할 어빌리티 (선택 사항)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 어빌리티를 제외한 모든 어빌리티를 취소합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어빌리티 차단 함수&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;BlockAbilitiesWithTags&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void BlockAbilitiesWithTags(const FGameplayTagContainer&amp;amp; Tags);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 태그를 가진 어빌리티를 차단합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Tags: 차단할 태그&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 태그를 가진 어빌리티의 활성화를 차단합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;UnBlockAbilitiesWithTags&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void UnBlockAbilitiesWithTags(const FGameplayTagContainer&amp;amp; Tags);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 태그를 가진 어빌리티의 차단을 해제합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Tags: 차단 해제할 태그&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 태그를 가진 어빌리티의 차단을 해제합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;BlockAbilityByInputID&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void BlockAbilityByInputID(int32 InputID);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 입력 ID를 가진 어빌리티를 차단합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InputID: 차단할 입력 ID&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 입력 ID에 바인딩된 어빌리티의 활성화를 차단합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;UnBlockAbilityByInputID&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void UnBlockAbilityByInputID(int32 InputID);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 입력 ID를 가진 어빌리티의 차단을 해제합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InputID: 차단 해제할 입력 ID&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 입력 ID에 바인딩된 어빌리티의 차단을 해제합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;IsAbilityInputBlocked&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;bool IsAbilityInputBlocked(int32 InputID) const;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 특정 입력 ID가 차단되었는지 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InputID: 확인할 입력 ID&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 차단 여부&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 입력 ID가 차단되었는지 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;입력 시스템 연동 함수&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;BindToInputComponent&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;virtual void BindToInputComponent(UInputComponent* InputComponent);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 어빌리티 시스템을 입력 컴포넌트에 바인딩합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InputComponent: 바인딩할 입력 컴포넌트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 기본 확인 및 취소 액션에 대한 바인딩을 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;BindAbilityActivationToInputComponent&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;virtual void BindAbilityActivationToInputComponent(UInputComponent* InputComponent, FGameplayAbilityInputBinds BindInfo);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 어빌리티 활성화를 입력 컴포넌트에 바인딩합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InputComponent: 바인딩할 입력 컴포넌트&lt;/li&gt;
&lt;li&gt;BindInfo: 바인딩 설정 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 입력 바인딩 정보를 사용하여 어빌리티를 입력에 바인딩합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AbilityLocalInputPressed&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;virtual void AbilityLocalInputPressed(int32 InputID);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 로컬 입력 누름 이벤트를 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InputID: 누른 입력 ID&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 입력 ID에 바인딩된 어빌리티의 입력 누름 이벤트를 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AbilityLocalInputReleased&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;virtual void AbilityLocalInputReleased(int32 InputID);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 로컬 입력 해제 이벤트를 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InputID: 해제한 입력 ID&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 입력 ID에 바인딩된 어빌리티의 입력 해제 이벤트를 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;PressInputID&lt;/h4&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, Category = &quot;Gameplay Abilities&quot;)
void PressInputID(int32 InputID);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 입력 ID를 프로그래밍 방식으로 누릅니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InputID: 누를 입력 ID&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 입력 ID를 프로그래밍 방식으로 누르는 것과 같은 효과를 냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ReleaseInputID&lt;/h4&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, Category = &quot;Gameplay Abilities&quot;)
void ReleaseInputID(int32 InputID);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 입력 ID를 프로그래밍 방식으로 해제합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InputID: 해제할 입력 ID&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 입력 ID를 프로그래밍 방식으로 해제하는 것과 같은 효과를 냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;액터 정보 설정 함수&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;InitAbilityActorInfo&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;virtual void InitAbilityActorInfo(AActor* InOwnerActor, AActor* InAvatarActor);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 어빌리티 액터 정보를 초기화합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InOwnerActor: 소유 액터&lt;/li&gt;
&lt;li&gt;InAvatarActor: 아바타 액터&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 소유 액터와 아바타 액터를 설정하고 어빌리티 액터 정보를 초기화합니다. 아바타 액터는 어빌리티가 실제로 작용하는 물리적 액터입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SetAvatarActor&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void SetAvatarActor(AActor* InAvatarActor);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 아바타 액터를 변경합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InAvatarActor: 새 아바타 액터&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 소유 액터는 그대로 두고 아바타 액터만 변경합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기타 어빌리티 관련 함수&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;GetAnimatingAbility&lt;/h4&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;UGameplayAbility* GetAnimatingAbility();
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 현재 애니메이션을 재생 중인 어빌리티를 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 애니메이션 중인 어빌리티&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 현재 몽타주를 재생 중인 어빌리티를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;IsAnimatingAbility&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;bool IsAnimatingAbility(UGameplayAbility* Ability) const;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 지정된 어빌리티가 현재 애니메이션을 재생 중인지 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ability: 확인할 어빌리티&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 애니메이션 중인지 여부&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 지정된 어빌리티가 현재 몽타주를 재생 중인지 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;GetUserAbilityActivationInhibited&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, Category=&quot;Abilities&quot;)
bool GetUserAbilityActivationInhibited() const;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 사용자 어빌리티 활성화가 억제되었는지 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 억제 여부&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 사용자 어빌리티 활성화가 억제되었는지 확인합니다. 이는 입력 처리나 UI 표시 등에 영향을 줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;SetUserAbilityActivationInhibited&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;UFUNCTION(BlueprintCallable, Category=&quot;Abilities&quot;)
virtual void SetUserAbilityActivationInhibited(bool NewInhibit);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 사용자 어빌리티 활성화 억제 상태를 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NewInhibit: 새 억제 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 사용자 어빌리티 활성화 억제 상태를 설정합니다. 이는 게임 메커니즘(침묵, 무력화 등)이 아닌 입력/UI 관련 억제에만 사용해야 합니다. 이 함수는 로컬 소유 플레이어에서만 호출되어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;게임플레이 이벤트 처리 함수&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;HandleGameplayEvent&lt;/h4&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;virtual int32 HandleGameplayEvent(FGameplayTag EventTag, const FGameplayEventData* Payload);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 게임플레이 이벤트를 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EventTag: 이벤트 태그&lt;/li&gt;
&lt;li&gt;Payload: 이벤트 데이터&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 성공적으로 활성화된 어빌리티 수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 게임플레이 이벤트를 처리하고 이벤트에 대응하는 어빌리티를 활성화합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TriggerAbilityFromGameplayEvent&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;bool TriggerAbilityFromGameplayEvent(FGameplayAbilitySpecHandle AbilityToTrigger, FGameplayAbilityActorInfo* ActorInfo, FGameplayTag Tag, const FGameplayEventData* Payload, UAbilitySystemComponent&amp;amp; Component);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 게임플레이 이벤트로부터 어빌리티를 트리거합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilityToTrigger: 트리거할 어빌리티 핸들&lt;/li&gt;
&lt;li&gt;ActorInfo: 액터 정보&lt;/li&gt;
&lt;li&gt;Tag: 이벤트 태그&lt;/li&gt;
&lt;li&gt;Payload: 이벤트 데이터&lt;/li&gt;
&lt;li&gt;Component: 어빌리티 시스템 컴포넌트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 트리거 성공 여부&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 게임플레이 이벤트를 사용하여 특정 어빌리티를 트리거합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네트워크 관련 함수&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;InternalServerTryActivateAbility&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;virtual void InternalServerTryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool InputPressed, const FPredictionKey&amp;amp; PredictionKey, const FGameplayEventData* TriggerEventData);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 서버에서 어빌리티 활성화를 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilityToActivate: 활성화할 어빌리티 핸들&lt;/li&gt;
&lt;li&gt;InputPressed: 입력이 눌렸는지 여부&lt;/li&gt;
&lt;li&gt;PredictionKey: 예측 키&lt;/li&gt;
&lt;li&gt;TriggerEventData: 이벤트 데이터 (선택 사항)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 서버에서 클라이언트 요청에 응답하여 어빌리티 활성화를 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ServerTryActivateAbility&lt;/h4&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;UFUNCTION(Server, reliable, WithValidation)
void ServerTryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate, bool InputPressed, FPredictionKey PredictionKey);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 서버에 어빌리티 활성화를 요청합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilityToActivate: 활성화할 어빌리티 핸들&lt;/li&gt;
&lt;li&gt;InputPressed: 입력이 눌렸는지 여부&lt;/li&gt;
&lt;li&gt;PredictionKey: 예측 키&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 클라이언트에서 서버에 어빌리티 활성화를 요청합니다. Server 태그는 이 함수가 클라이언트에서 서버로의 RPC임을 나타냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ClientTryActivateAbility&lt;/h4&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UFUNCTION(Client, reliable)
void ClientTryActivateAbility(FGameplayAbilitySpecHandle AbilityToActivate);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 클라이언트에게 어빌리티 활성화를 요청합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilityToActivate: 활성화할 어빌리티 핸들&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 서버에서 클라이언트에게 어빌리티 활성화를 요청합니다. Client 태그는 이 함수가 서버에서 클라이언트로의 RPC임을 나타냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ServerEndAbility / ClientEndAbility&lt;/h4&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UFUNCTION(Server, reliable, WithValidation)
void ServerEndAbility(FGameplayAbilitySpecHandle AbilityToEnd, FGameplayAbilityActivationInfo ActivationInfo, FPredictionKey PredictionKey);

UFUNCTION(Client, reliable)
void ClientEndAbility(FGameplayAbilitySpecHandle AbilityToEnd, FGameplayAbilityActivationInfo ActivationInfo);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 어빌리티 종료를 네트워크에 복제합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilityToEnd: 종료할 어빌리티 핸들&lt;/li&gt;
&lt;li&gt;ActivationInfo: 활성화 정보&lt;/li&gt;
&lt;li&gt;PredictionKey: 예측 키 (서버 함수만 해당)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 어빌리티 종료를 네트워크에 복제하여 모든 클라이언트가 동일한 상태를 유지하도록 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ServerCancelAbility / ClientCancelAbility&lt;/h4&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UFUNCTION(Server, reliable, WithValidation)
void ServerCancelAbility(FGameplayAbilitySpecHandle AbilityToCancel, FGameplayAbilityActivationInfo ActivationInfo);

UFUNCTION(Client, reliable)
void ClientCancelAbility(FGameplayAbilitySpecHandle AbilityToCancel, FGameplayAbilityActivationInfo ActivationInfo);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 어빌리티 취소를 네트워크에 복제합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilityToCancel: 취소할 어빌리티 핸들&lt;/li&gt;
&lt;li&gt;ActivationInfo: 활성화 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 어빌리티 취소를 네트워크에 복제하여 모든 클라이언트가 동일한 상태를 유지하도록 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ClientActivateAbilityFailed&lt;/h4&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UFUNCTION(Client, Reliable)
void ClientActivateAbilityFailed(FGameplayAbilitySpecHandle AbilityToActivate, int16 PredictionKey);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 어빌리티 활성화 실패를 클라이언트에게 알립니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilityToActivate: 활성화 실패한 어빌리티 핸들&lt;/li&gt;
&lt;li&gt;PredictionKey: 예측 키&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 서버에서 클라이언트의 어빌리티 활성화 요청이 실패했을 때 클라이언트에게 알립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ClientActivateAbilitySucceed&lt;/h4&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;UFUNCTION(Client, Reliable)
void ClientActivateAbilitySucceed(FGameplayAbilitySpecHandle AbilityToActivate, FPredictionKey PredictionKey);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 어빌리티 활성화 성공을 클라이언트에게 알립니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilityToActivate: 활성화 성공한 어빌리티 핸들&lt;/li&gt;
&lt;li&gt;PredictionKey: 예측 키&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 서버에서 클라이언트의 어빌리티 활성화 요청이 성공했을 때 클라이언트에게 알립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CallServerTryActivateAbility / CallServerEndAbility&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void CallServerTryActivateAbility(FGameplayAbilitySpecHandle AbilityHandle, bool InputPressed, FPredictionKey PredictionKey);
void CallServerEndAbility(FGameplayAbilitySpecHandle AbilityHandle, FGameplayAbilityActivationInfo ActivationInfo, FPredictionKey PredictionKey);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 내부적으로 서버 RPC를 호출합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AbilityHandle: 어빌리티 핸들&lt;/li&gt;
&lt;li&gt;InputPressed / ActivationInfo: 입력 상태 / 활성화 정보&lt;/li&gt;
&lt;li&gt;PredictionKey: 예측 키&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 이 함수들은 RPC 배치 처리를 지원하는 래퍼 함수입니다. 일반적인 함수 호출 시에는 바로 RPC를 호출하지만, 배치 모드에서는 여러 RPC를 모아서 한 번에 보냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;몽타주 관련 함수&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;PlayMontage&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;virtual float PlayMontage(UGameplayAbility* AnimatingAbility, FGameplayAbilityActivationInfo ActivationInfo, UAnimMontage* Montage, float InPlayRate, FName StartSectionName = NAME_None, float StartTimeSeconds = 0.0f);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 어빌리티와 연결된 몽타주를 재생합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AnimatingAbility: 애니메이션을 재생하는 어빌리티&lt;/li&gt;
&lt;li&gt;ActivationInfo: 활성화 정보&lt;/li&gt;
&lt;li&gt;Montage: 재생할 몽타주&lt;/li&gt;
&lt;li&gt;InPlayRate: 재생 속도&lt;/li&gt;
&lt;li&gt;StartSectionName: 시작 섹션 이름 (선택 사항)&lt;/li&gt;
&lt;li&gt;StartTimeSeconds: 시작 시간 (선택 사항)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 몽타주 지속 시간 (-1은 실패)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 어빌리티와 연결된 몽타주를 재생하고, 네트워크 복제 및 예측을 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;GetCurrentMontage&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;UAnimMontage* GetCurrentMontage() const;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 현재 재생 중인 몽타주를 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;반환값&lt;/b&gt;: 현재 몽타주 (없으면 nullptr)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 현재 재생 중인 몽타주를 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CurrentMontageStop&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;virtual void CurrentMontageStop(float OverrideBlendOutTime = -1.0f);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;목적&lt;/b&gt;: 현재 재생 중인 몽타주를 중지합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매개변수&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OverrideBlendOutTime: 블렌드 아웃 시간 오버라이드 (선택 사항)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;설명&lt;/b&gt;: 현재 재생 중인 몽타주를 중지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 예시&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 기본 어빌리티 시스템 설정&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 액터 클래스 내부
UPROPERTY()
UAbilitySystemComponent* AbilitySystemComponent;

// 생성자나 BeginPlay에서
AbilitySystemComponent = CreateDefaultSubobject&amp;lt;UAbilitySystemComponent&amp;gt;(TEXT(&quot;AbilitySystemComponent&quot;));
AbilitySystemComponent-&amp;gt;InitAbilityActorInfo(this, this);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 어빌리티 부여 및 활성화&lt;/h3&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;// 블루프린트에서 호출할 수 있는 함수
UFUNCTION(BlueprintCallable, Category = &quot;Abilities&quot;)
void GiveFireballAbility()
{
    if (AbilitySystemComponent &amp;amp;&amp;amp; HasAuthority())
    {
        // 어빌리티 부여
        FGameplayAbilitySpecHandle AbilityHandle = AbilitySystemComponent-&amp;gt;K2_GiveAbility(
            FireballAbilityClass,  // TSubclassOf&amp;lt;UGameplayAbility&amp;gt;
            1,                     // 레벨
            0                      // 입력 ID
        );
        
        // 입력 바인딩을 사용하지 않고 직접 활성화할 수도 있음
        AbilitySystemComponent-&amp;gt;TryActivateAbility(AbilityHandle);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 입력과 어빌리티 연결&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// 입력 컴포넌트 설정 시
void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
    
    // 어빌리티 입력 바인딩 설정
    if (AbilitySystemComponent)
    {
        AbilitySystemComponent-&amp;gt;BindAbilityActivationToInputComponent(
            PlayerInputComponent,
            FGameplayAbilityInputBinds(
                &quot;Confirm&quot;,       // 확인 명령어
                &quot;Cancel&quot;,        // 취소 명령어
                &quot;EAbilityInputID&quot;, // 바인딩 열거형
                static_cast&amp;lt;int32&amp;gt;(EAbilityInputID::Confirm),  // 확인 입력 ID
                static_cast&amp;lt;int32&amp;gt;(EAbilityInputID::Cancel)    // 취소 입력 ID
            )
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 태그 기반 어빌리티 활성화&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// 특정 태그의 모든 어빌리티 활성화
void AMyCharacter::ActivateAllFireAbilities()
{
    if (AbilitySystemComponent)
    {
        FGameplayTagContainer FireTags;
        FireTags.AddTag(FGameplayTag::RequestGameplayTag(FName(&quot;Ability.Fire&quot;)));
        
        AbilitySystemComponent-&amp;gt;TryActivateAbilitiesByTag(FireTags);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 어빌리티 차단 및 취소&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;// 특정 태그의 어빌리티 차단
void AMyCharacter::BlockFireAbilities()
{
    if (AbilitySystemComponent)
    {
        FGameplayTagContainer FireTags;
        FireTags.AddTag(FGameplayTag::RequestGameplayTag(FName(&quot;Ability.Fire&quot;)));
        
        // 화염 태그가 있는 어빌리티 차단
        AbilitySystemComponent-&amp;gt;BlockAbilitiesWithTags(FireTags);
        
        // 화염 태그가 있는 활성화된 어빌리티 취소
        AbilitySystemComponent-&amp;gt;CancelAbilities(&amp;amp;FireTags);
    }
}

// 차단 해제
void AMyCharacter::UnblockFireAbilities()
{
    if (AbilitySystemComponent)
    {
        FGameplayTagContainer FireTags;
        FireTags.AddTag(FGameplayTag::RequestGameplayTag(FName(&quot;Ability.Fire&quot;)));
        
        AbilitySystemComponent-&amp;gt;UnBlockAbilitiesWithTags(FireTags);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 게임플레이 이벤트로 어빌리티 트리거&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// 게임플레이 이벤트로 어빌리티 트리거
void AMyCharacter::TriggerAbilityFromDamage(float Damage, FGameplayTag DamageTag)
{
    if (AbilitySystemComponent)
    {
        FGameplayEventData EventData;
        EventData.EventTag = DamageTag;
        EventData.EventMagnitude = Damage;
        
        AbilitySystemComponent-&amp;gt;HandleGameplayEvent(DamageTag, &amp;amp;EventData);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;베스트 프랙티스 및 주의사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 라이프사이클 관리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;컴포넌트 초기화&lt;/b&gt;: InitAbilityActorInfo를 반드시 호출하여 어빌리티 시스템을 초기화해야 합니다. 일반적으로 BeginPlay나 OnPossessed에서 호출합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴포넌트 정리&lt;/b&gt;: DestroyActiveState를 사용하여 모든 활성 어빌리티를 취소하고 정리합니다. 이는 일반적으로 EndPlay나 OnUnpossessed에서 호출합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;포제션(Possession) 변경 처리&lt;/b&gt;: 컨트롤러 변경 시 SetAvatarActor를 호출하여 새 컨트롤러에 맞게 어빌리티 시스템을 업데이트합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 네트워크 관련 주의사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;권한 확인&lt;/b&gt;: 어빌리티 부여 및 제거는 서버에서만 수행해야 합니다. IsOwnerActorAuthoritative를 사용하여 권한을 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예측 모드&lt;/b&gt;: 로컬 예측 모드로 설정된 어빌리티는 클라이언트에서 실행되고 서버에서 확인됩니다. 이는 빠른 응답성을 제공하지만 복잡성이 증가합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복제 모드&lt;/b&gt;: SetReplicationMode를 사용하여 어빌리티 시스템의 복제 모드를 설정합니다. 일반적으로 Full 모드가 가장 안전하지만, 네트워크 대역폭을 더 많이 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 성능 고려사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;어빌리티 수&lt;/b&gt;: 활성화 가능한 어빌리티 수를 관리하세요. 너무 많은 어빌리티는 메모리 및 복제 비용을 증가시킵니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;태그 쿼리 최적화&lt;/b&gt;: 빈번한 태그 쿼리는 성능에 영향을 줄 수 있습니다. 가능한 경우 결과를 캐시하거나 쿼리 빈도를 제한하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예측 키 관리&lt;/b&gt;: 예측 키를 올바르게 관리하지 않으면 네트워크 성능과 게임플레이 경험에 영향을 줄 수 있습니다. 예측 키를 생성하고 전파하는 방법에 주의하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 어빌리티 관리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인스턴스 정책&lt;/b&gt;: 어빌리티 인스턴스 정책(NonInstanced, InstancedPerActor, InstancedPerExecution)을 신중하게 선택하세요. 각 정책은 메모리 사용량과 동작 방식에 영향을 줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실행 정책&lt;/b&gt;: 어빌리티 실행 정책(LocalOnly, LocalPredicted, ServerOnly, ServerInitiated)에 따라 네트워크 동작이 달라집니다. 각 어빌리티의 요구사항에 맞는 정책을 선택하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;태그 요구사항&lt;/b&gt;: 어빌리티의 태그 요구사항(Required, Blocked)을 사용하여 어빌리티 간 상호작용을 관리하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 디버깅&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;PrintDebug&lt;/b&gt;: AbilitySystemComponent::PrintDebug()를 사용하여 어빌리티 시스템의 상태를 디버깅합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로깅&lt;/b&gt;: ABILITY_LOG 매크로를 사용하여 어빌리티 시스템 관련 로그를 출력합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시각적 디버깅&lt;/b&gt;: 게임플레이 디버거를 사용하여 실행 중인 어빌리티 및 이펙트를 시각적으로 디버깅합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추가 참고 사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 확장 및 커스터마이징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;커스텀 어빌리티 시스템 컴포넌트&lt;/b&gt;: 필요에 따라 UAbilitySystemComponent를 상속받아 커스텀 기능을 추가할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;가상 함수 오버라이드&lt;/b&gt;: InternalTryActivateAbility, NotifyAbilityEnded 등의 가상 함수를 오버라이드하여 어빌리티 활성화 및 종료 시 커스텀 로직을 추가할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;콜백 활용&lt;/b&gt;: AbilityActivatedCallbacks, AbilityEndedCallbacks 등의 델리게이트를 활용하여 어빌리티 이벤트에 반응할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 다른 시스템과의 통합&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;애니메이션 시스템&lt;/b&gt;: PlayMontage, GetCurrentMontage 등의 함수를 사용하여 어빌리티와 애니메이션을 연동할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;입력 시스템&lt;/b&gt;: BindAbilityActivationToInputComponent를 사용하여 어빌리티와 입력을 연동할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;게임플레이 태그&lt;/b&gt;: 태그 시스템을 활용하여 어빌리티 간 상호작용을 관리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;어트리뷰트 시스템&lt;/b&gt;: 어빌리티 시스템 컴포넌트는 어트리뷰트 세트를 관리하여 캐릭터 스탯과 어빌리티를 연동할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 고급 기능&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;RPC 배치 처리&lt;/b&gt;: BeginServerAbilityRPCBatch와 EndServerAbilityRPCBatch를 사용하여 여러 RPC를 배치로 처리할 수 있습니다. 이는 네트워크 성능을 향상시킬 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;예측 시스템&lt;/b&gt;: FScopedPredictionWindow를 사용하여 클라이언트 예측 및 서버 확인을 관리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;어빌리티 태스크&lt;/b&gt;: 어빌리티 내에서 UAbilityTask를 사용하여 비동기 작업을 처리할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 알려진 제한사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;복잡한 복제 로직&lt;/b&gt;: 어빌리티 시스템의 복제 로직은 복잡하며, 특히 비표준 네트워크 설정에서 문제가 발생할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디버깅 어려움&lt;/b&gt;: 예측 및 네트워크 관련 문제는 디버깅이 어려울 수 있습니다. 디버깅 도구와 로깅을 적극적으로 활용하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실행 순서 의존성&lt;/b&gt;: 어빌리티 시스템의 많은 부분은 실행 순서에 의존합니다. 초기화 및 종료 순서를 신중하게 관리하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 API 가이드는 AbilitySystemComponent의 어빌리티 관련 기능에 대한 개요를 제공합니다. 게임의 요구사항에 맞게 어빌리티 시스템을 효과적으로 활용하려면 이 가이드와 함께 공식 문서와 예제를 참조하는 것이 좋습니다.&lt;/p&gt;</description>
      <category>개발일지(Unreal5)/GAS[Gameplay Ability System]</category>
      <author>남찬우</author>
      <guid isPermaLink="true">https://kakaoncw.tistory.com/96</guid>
      <comments>https://kakaoncw.tistory.com/96#entry96comment</comments>
      <pubDate>Wed, 26 Mar 2025 13:08:56 +0900</pubDate>
    </item>
    <item>
      <title>UGameplayEffectExecutionCalculation</title>
      <link>https://kakaoncw.tistory.com/95</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;UGameplayEffectExecutionCalculation 개요 및 활용 가이드&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문서는 언리얼 엔진(UE)의 **Gameplay Ability System(GAS)**에서 UGameplayEffectExecutionCalculation을 활용하는 방법을 안내합니다. 역할과 목적, 사용 예제, 관련 클래스 간 관계, 그리고 최적화 및 커스터마이징 팁 등을 다룹니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 역할 및 목적&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UGameplayEffectExecutionCalculation(이하 &lt;b&gt;ExecutionCalculation&lt;/b&gt;)은 &lt;b&gt;GameplayEffect&lt;/b&gt;에 특수한 실행 로직을 부여하기 위한 클래스입니다.&lt;br /&gt;일반적인 &lt;b&gt;Modifier&lt;/b&gt;만으로 처리하기 어려운 &lt;b&gt;복잡한 계산 로직&lt;/b&gt;(예: &lt;b&gt;피해량 계산&lt;/b&gt;, &lt;b&gt;조건부 효과 발동&lt;/b&gt;)을 구현할 때 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;즉시(Instant) 또는 주기적(Periodic) 효과&lt;/b&gt;에서만 실행됩니다.&lt;/li&gt;
&lt;li&gt;효과가 발동될 때마다 &lt;b&gt;한 번&lt;/b&gt;만 수행되어, 필요한 &lt;b&gt;Attribute&lt;/b&gt;들을 &lt;b&gt;캡처(capture)&lt;/b&gt; 후 계산에 활용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스냅샷(Snapshot)&lt;/b&gt; 여부를 설정해 시점에 따라 다른 Attribute 값을 참조할 수 있습니다.&lt;br /&gt;- true이면 &lt;b&gt;Spec 생성 시&lt;/b&gt; 값 고정,&lt;br /&gt;- false이면 &lt;b&gt;실제 실행 시점&lt;/b&gt;의 최신 값 참조.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의:&lt;/b&gt;&lt;br /&gt;ExecutionCalculation은 &lt;b&gt;클라이언트 예측이 불가&lt;/b&gt;하고, &lt;b&gt;C++ 구현&lt;/b&gt;이 사실상 권장됩니다. (BlueprintNativeClass로서 Blueprint 상속은 가능하지만, 성능 및 복잡도 면에서 C++가 더 적합)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 주요 기능과 동작 원리&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 Execute_Implementation 함수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ExecutionCalculation은 Execute_Implementation 오버라이드로, &lt;b&gt;효과 발동 시점&lt;/b&gt;에 수행할 계산 로직을 정의합니다. 함수 인자는 다음과 같습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;FGameplayEffectCustomExecutionParameters&amp;amp; ExecutionParams
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행에 필요한 &lt;b&gt;GameplayEffectSpec&lt;/b&gt;, &lt;b&gt;Source/Target&lt;/b&gt;의 AbilitySystemComponent, &lt;b&gt;캡처된 태그&lt;/b&gt; 등을 조회 가능&lt;/li&gt;
&lt;li&gt;예:
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const FGameplayEffectSpec&amp;amp; Spec = ExecutionParams.GetOwningSpec();
auto* TargetASC = ExecutionParams.GetTargetAbilitySystemComponent();
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;FGameplayEffectCustomExecutionOutput&amp;amp; OutExecutionOutput
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;계산 결과&lt;/b&gt;를 담아, 최종적으로 어떤 Attribute를 어떻게 변경할지 지정&lt;/li&gt;
&lt;li&gt;예:
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;OutExecutionOutput.AddOutputModifier(
  FGameplayModifierEvaluatedData(Attribute, EGameplayModOp::Additive, Value)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 Attribute 캡처 &amp;amp; 스냅샷&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Attribute 캡처&lt;/b&gt;: 사전에 등록된 캡처 정의에 따라 &lt;b&gt;Source/Target&lt;/b&gt;의 Attribute 값을 가져옵니다.&lt;br /&gt;- ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(...)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스냅샷&lt;/b&gt;: EGameplayEffectAttributeCaptureDefinition에 명시된 bool bSnapshot 값에 따라, Spec 생성 시점 또는 실행 시점 값을 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.3 결과 적용 방식&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OutExecutionOutput.AddOutputModifier(...)로 원하는 &lt;b&gt;Attribute&lt;/b&gt;에 &lt;b&gt;증가/감소&lt;/b&gt; 값을 지정합니다.&lt;/li&gt;
&lt;li&gt;하나의 ExecutionCalculation에서 &lt;b&gt;여러 Attribute&lt;/b&gt;를 동시에 변경할 수도 있습니다.&lt;/li&gt;
&lt;li&gt;MarkConditionalGameplayEffectsToTrigger(): &lt;b&gt;Conditional GameplayEffect&lt;/b&gt; 조건을 만족하면 추가 효과를 발동하도록 지시합니다.&lt;/li&gt;
&lt;li&gt;MarkStackCountHandledManually(), MarkGameplayCuesHandledManually(): 중첩(Stack)이나 GameplayCue를 &lt;b&gt;수동으로 처리&lt;/b&gt;할 때 사용합니다. (특수 케이스)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 사용 예제 및 구현 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 &lt;b&gt;데미지 계산&lt;/b&gt; 로직을 담은 ExecutionCalculation 예시를 간단히 살펴봅니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;버전&lt;/b&gt;: Unreal Engine 5 기준&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 클래스 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++에서 UGameplayEffectExecutionCalculation을 상속한 클래스를 생성합니다. 예:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;UCLASS()
class UMyDamageExecutionCalculation : public UGameplayEffectExecutionCalculation
{
    GENERATED_BODY()

public:
    UMyDamageExecutionCalculation();

protected:
    virtual void Execute_Implementation(
      const FGameplayEffectCustomExecutionParameters&amp;amp; ExecutionParams,
      FGameplayEffectCustomExecutionOutput&amp;amp; OutExecutionOutput
    ) const override;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 Attribute 캡처 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ExecutionCalculation에서는 &lt;b&gt;어떤 Attribute&lt;/b&gt;를 캡처해 올지 정의해야 합니다.&lt;br /&gt;일반적으로 정적 구조체를 만들어 &lt;b&gt;AttackPower&lt;/b&gt;, &lt;b&gt;DefensePower&lt;/b&gt; 등 필요한 속성을 지정한 뒤, 클래스 생성자에서 RelevantAttributesToCapture.Add(...)로 등록합니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;struct FDamageStatics
{
    FGameplayEffectAttributeCaptureDefinition AttackPowerDef;
    FGameplayEffectAttributeCaptureDefinition DefensePowerDef;
    FGameplayEffectAttributeCaptureDefinition DamageDef;

    FDamageStatics()
    {
        AttackPowerDef = FGameplayEffectAttributeCaptureDefinition(
            UMyAttributeSet::GetAttackPowerAttribute(),
            EGameplayEffectAttributeCaptureSource::Source,
            true // 스냅샷 사용 여부
        );

        DefensePowerDef = FGameplayEffectAttributeCaptureDefinition(
            UMyAttributeSet::GetDefensePowerAttribute(),
            EGameplayEffectAttributeCaptureSource::Target,
            false
        );

        DamageDef = FGameplayEffectAttributeCaptureDefinition(
            UMyAttributeSet::GetDamageAttribute(),
            EGameplayEffectAttributeCaptureSource::Target,
            false
        );
    }
};

static const FDamageStatics&amp;amp; DamageStatics()
{
    static FDamageStatics Statics;
    return Statics;
}

// ---

UMyDamageExecutionCalculation::UMyDamageExecutionCalculation()
{
    RelevantAttributesToCapture.Add(DamageStatics().AttackPowerDef);
    RelevantAttributesToCapture.Add(DamageStatics().DefensePowerDef);
    RelevantAttributesToCapture.Add(DamageStatics().DamageDef);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.3 Execute_Implementation 로직&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 값을 불러오고, 최종 값을 OutExecutionOutput에 반영합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void UMyDamageExecutionCalculation::Execute_Implementation(
    const FGameplayEffectCustomExecutionParameters&amp;amp; ExecutionParams,
    FGameplayEffectCustomExecutionOutput&amp;amp; OutExecutionOutput
) const
{
    const FGameplayEffectSpec&amp;amp; Spec = ExecutionParams.GetOwningSpec();

    FAggregatorEvaluateParameters EvalParams;
    EvalParams.SourceTags = Spec.CapturedSourceTags.GetAggregatedTags();
    EvalParams.TargetTags = Spec.CapturedTargetTags.GetAggregatedTags();

    // 캡처된 공격력, 방어력 가져오기
    float AttackPower = 0.f;
    ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
        DamageStatics().AttackPowerDef, EvalParams, AttackPower);

    float DefensePower = 0.f;
    ExecutionParams.AttemptCalculateCapturedAttributeMagnitude(
        DamageStatics().DefensePowerDef, EvalParams, DefensePower);

    // SetByCaller로 전달된 추가 피해
    float BaseDamage = Spec.GetSetByCallerMagnitude(
        FGameplayTag::RequestGameplayTag(&quot;Data.Damage&quot;),
        /*bWarnIfNotFound=*/false,
        0.f
    );

    // 간단한 예시: (공격력 + 추가 피해) - 방어력
    float DamageDone = FMath::Max(BaseDamage + AttackPower - DefensePower, 0.0f);

    if (DamageDone &amp;gt; 0.f)
    {
        OutExecutionOutput.AddOutputModifier(FGameplayModifierEvaluatedData(
            UMyAttributeSet::GetDamageAttribute(),
            EGameplayModOp::Additive,
            DamageDone
        ));

        // 예: 추가 효과(상태이상 등)가 있을 때만 발동
        OutExecutionOutput.MarkConditionalGameplayEffectsToTrigger();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 다른 GAS 클래스와의 관계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;UGameplayEffect&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;지속시간, 수치 Modifier, 적용 대상을 정의하는 &lt;b&gt;효과 오브젝트&lt;/b&gt;입니다. 여기서 Executions 배열에 ExecutionCalculation을 연결할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;UAttributeSet&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;게임 플레이에 쓰이는 **속성(Attribute)**을 관리하는 클래스입니다. ExecutionCalculation은 여기서 &lt;b&gt;캡처&lt;/b&gt;한 값으로 연산하고, 최종 결과도 다시 &lt;b&gt;AttributeSet&lt;/b&gt;에 반영합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;UGameplayAbility&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;특정 액션(스킬)을 표현하는 클래스입니다. 일반적으로 이 &lt;b&gt;Ability&lt;/b&gt;에서 GameplayEffect를 생성 및 적용합니다. 필요 시 &lt;b&gt;SetByCaller&lt;/b&gt; 등으로 추가 데이터를 전달할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;UAbilitySystemComponent&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;GameplayEffect, Ability, Attribute 변화를 종합 관리하는 핵심 컴포넌트입니다. &lt;b&gt;즉시형(Instant) GE&lt;/b&gt;를 적용할 때 ExecutionCalculation이 실행됩니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 최적화 및 고급 활용 팁&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;캡처 정의 최소화&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;불필요한 Attribute는 캡처하지 말고, 필요한 항목만 등록해 &lt;b&gt;성능&lt;/b&gt;을 높입니다.&lt;/li&gt;
&lt;li&gt;캡처 정의를 여러 ExecutionCalculation에서 재사용할 때는 정적 구조체를 활용하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스냅샷 기능&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;시점별&lt;/b&gt;로 값이 달라질 수 있는 속성은 bSnapshot = false로 설정하여 &lt;b&gt;실시간 캡처&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;예측(예: 투사체 발사 후 도달까지 시간 차)이 필요한 경우 특정 속성만 미리 스냅샷을 활용해 정확도와 성능을 조절하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Blueprint vs C++&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C++이 &lt;b&gt;권장&lt;/b&gt;됩니다. (성능, 유지보수, 서버 로직 디버깅 측면)&lt;/li&gt;
&lt;li&gt;Blueprint로 서브클래스화는 가능하나, 복잡한 수학 계산 등에 적합하지 않을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SetByCaller &amp;amp; GameplayEffectContext&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동적 데이터&lt;/b&gt;(예: 무기 공격력, 콤보 스택)를 ExecutionCalculation에 전달할 때 유용합니다.&lt;/li&gt;
&lt;li&gt;필요 시 &lt;b&gt;GameplayEffectContext&lt;/b&gt;를 확장하여 HitResult 등 &lt;b&gt;추가 정보&lt;/b&gt;를 전달할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;조건부 효과 &amp;amp; 수동 처리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MarkConditionalGameplayEffectsToTrigger(): 특정 조건이 충족될 때만 &lt;b&gt;추가 효과&lt;/b&gt;를 발동.&lt;/li&gt;
&lt;li&gt;MarkStackCountHandledManually(): 중첩(Stack) 로직을 &lt;b&gt;수동&lt;/b&gt; 관리.&lt;/li&gt;
&lt;li&gt;MarkGameplayCuesHandledManually(): Cue 처리를 별도 제어.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디버깅 &amp;amp; 테스트&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ExecutionCalculation은 &lt;b&gt;서버 전용&lt;/b&gt;이므로, Net 환경에서 디버깅 시 로그를 적극 활용하세요.&lt;/li&gt;
&lt;li&gt;단일 플레이어 테스트로 수치를 점검한 뒤 멀티플레이에서 검증하는 방식이 안전합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 결론 및 참고 자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UGameplayEffectExecutionCalculation을 통해 &lt;b&gt;데미지, 치유, 상태 이상&lt;/b&gt; 등 복잡한 로직을 깔끔하게 관리할 수 있습니다. 하나의 클래스에서 공식과 로직을 집중적으로 다룰 수 있어, &lt;b&gt;밸런스 조정&lt;/b&gt;과 &lt;b&gt;멀티플레이어 동기화&lt;/b&gt;에도 강력한 이점을 제공합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고 자료&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.unrealengine.com/5.0/en-US/gameplay-ability-system-in-unreal-engine/&quot;&gt;Unreal Engine 공식 문서(GAS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/tranek/GASDocumentation&quot;&gt;Tranek의 GASDocumentation GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.thegames.dev/?p=119&quot;&gt;The Games Dev 블로그: &amp;ldquo;Understanding Gameplay Effect Execution Calculations&amp;rdquo;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://redchiken.tistory.com/376&quot;&gt;redchiken 블로그: GAS 관련 정리 (KOR)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/EpicGames/ActionRPG&quot;&gt;Unreal Engine 샘플 (Action RPG 프로젝트 등)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상의 내용을 참고하여, 적절한 UGameplayEffectExecutionCalculation을 구현하시기 바랍니다.&lt;br /&gt;궁극적으로 &lt;b&gt;더 직관적이고 유지보수성 높은&lt;/b&gt; GAS 로직을 구성하는 데 많은 도움이 될 것입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ⓒ 2023.&lt;/b&gt; (정보 출처: Unreal Engine 문서, 개발자 블로그, 샘플 프로젝트 등)&lt;/p&gt;</description>
      <category>개발일지(Unreal5)/GAS[Gameplay Ability System]</category>
      <author>남찬우</author>
      <guid isPermaLink="true">https://kakaoncw.tistory.com/95</guid>
      <comments>https://kakaoncw.tistory.com/95#entry95comment</comments>
      <pubDate>Sat, 22 Mar 2025 16:46:43 +0900</pubDate>
    </item>
    <item>
      <title>#3 간단한 움직이는 스프라이트</title>
      <link>https://kakaoncw.tistory.com/94</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;아래 글은 언리얼 엔진 나이아가라(Niagara)에서 &lt;b&gt;스프라이트 파티클을 움직이는 방법&lt;/b&gt;을 알아보는 강의 내용을 정리한 것입니다. 지난 강의에서 만들었던 단순한 스프라이트 파티클을 확장하여, &lt;b&gt;Velocity(속도)를 부여&lt;/b&gt;하고 &lt;b&gt;여러 개 파티클을 반복해서 스폰&lt;/b&gt;하는 과정을 단계별로 살펴봅니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 지난 강의 복습&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;나이아가라 시스템 vs. 이미터&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;나이아가라 시스템(Niagara System): 레벨에 실제로 &lt;b&gt;배치&lt;/b&gt; 가능한 &amp;ldquo;컨테이너&amp;rdquo;&lt;/li&gt;
&lt;li&gt;나이아가라 이미터(Niagara Emitter): 파티클을 &lt;b&gt;생성&lt;/b&gt;하고 &lt;b&gt;제어&lt;/b&gt;하는 단위 (시스템 안에 여러 개가 들어갈 수 있음)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Spawner 모듈(Spawn Burst vs. Spawn Rate)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spawn Burst Instantaneous: &lt;b&gt;한 번&lt;/b&gt;에 특정 개수의 파티클을 생성&lt;/li&gt;
&lt;li&gt;Spawn Rate: &lt;b&gt;시간&lt;/b&gt;에 따라 일정한 비율로 파티클을 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Velocity(속도) 부여&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Add Velocity 모듈로 파티클이 &lt;b&gt;한 방향&lt;/b&gt;으로 &lt;b&gt;움직이도록&lt;/b&gt; 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Simple Sprite 템플릿으로 나이아가라 시스템 만들기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 폴더 구조 정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새 폴더 생성: 02-SimpleMovingSprite&lt;/li&gt;
&lt;li&gt;이전 강의와 달리, &lt;b&gt;이미터&lt;/b&gt;부터가 아닌 &lt;b&gt;시스템&lt;/b&gt;부터 생성해볼 예정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 나이아가라 시스템 생성&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;콘텐츠 브라우저 우클릭 &amp;rarr; &lt;b&gt;FX &amp;gt; Niagara System&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;팝업 창에서 &lt;b&gt;&amp;ldquo;템플릿 선택(Create a new system from selected emitters or template)&amp;rdquo;&lt;/b&gt; 옵션 선택&lt;/li&gt;
&lt;li&gt;템플릿 중 **Simple Sprite Burst**를 선택
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;미리 정의된 &amp;ldquo;스프라이트 버스트&amp;rdquo; 이미터가 추가됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;시스템 이름 예: NS_Simple_MovingSprite&lt;/li&gt;
&lt;li&gt;열어보면, &lt;b&gt;파티클 수명(2초), 크기(50), Burst 스폰(기본 1개)&lt;/b&gt; 등의 설정이 이미 준비되어 있음&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 파티클 움직이기: Add Velocity 모듈&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) Add Velocity 모듈 추가&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Particle Spawn&lt;/b&gt; 스테이지 &amp;rarr; + 버튼 &amp;rarr; Add Velocity&lt;/li&gt;
&lt;li&gt;기본적으로 Z축 방향(위쪽)으로 50 유닛 속도 적용&lt;/li&gt;
&lt;li&gt;Velocity Scale로 속도 배율 조정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 여러 파티클 스폰하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Spawn Burst&lt;/b&gt;만 있으면 &lt;b&gt;한 번&lt;/b&gt;에 N개 생성하고 끝남&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Spawn Rate&lt;/b&gt;로 교체하면, &lt;b&gt;프레임마다 일정 개수&lt;/b&gt;씩 지속적으로 생성됨
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Emitter Update&lt;/b&gt; 스테이지에서 Spawn Burst Instantaneous 모듈 삭제&lt;/li&gt;
&lt;li&gt;Spawn Rate 모듈 추가 &amp;rarr; 스폰 속도(예: 10)&lt;/li&gt;
&lt;li&gt;이제 파티클이 1초에 10개씩 생성되고, 각 파티클에 Add Velocity가 적용되어 위로 움직임&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 파티클 크기 조정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Initialize Particle&lt;/b&gt; 모듈에서 기본 스프라이트 크기를 줄일 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: Sprite Size를 50에서 25, 15 등으로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 레벨에 시스템 배치 &amp;amp; 무한 루프 설정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 레벨 배치&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뷰포트에서 &lt;b&gt;나이아가라 시스템&lt;/b&gt;(NS_Simple_MovingSprite)을 드래그 앤 드롭&lt;/li&gt;
&lt;li&gt;또는 월드 아웃라이너에서 + 버튼으로 액터 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 루프 비헤이비어(Emitter State)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본값이 Once여서 한 번만 스폰 후 종료됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Emitter State&lt;/b&gt; 모듈 &amp;rarr; &lt;b&gt;Loop Behavior&lt;/b&gt;를 Infinite로 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무한히 스폰되어 파티클 효과가 계속 나타남&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;또 다른 방법: **수명 모드(Lifetime Mode)**를 System으로 바꾸고, 시스템 자체를 무한 재생
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템 패널에서 &lt;b&gt;Loop Behavior&lt;/b&gt;를 무한(5초 간격 등)으로 반복시키는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 예시 데모 디스플레이 활용&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레벨에 이미 있는 BP_Demo_Display 액터를 &lt;b&gt;Alt + 드래그&lt;/b&gt; 해서 복제&lt;/li&gt;
&lt;li&gt;**제목(Title)**만 바꾸거나, &lt;b&gt;텍스트(Description)&lt;/b&gt; 항목을 편집해 해당 파티클 효과를 설명&lt;/li&gt;
&lt;li&gt;키보드 G를 누르면 게임 모드 뷰로 전환되어 파티클만 깔끔하게 확인 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 도전 과제&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Simple Sprite Burst&lt;/b&gt; 템플릿으로 새 나이아가라 시스템 생성&lt;/li&gt;
&lt;li&gt;파티클을 &lt;b&gt;여러 개&lt;/b&gt; 스폰하고, Add Velocity 모듈로 움직이도록 만들기&lt;/li&gt;
&lt;li&gt;레벨에 배치하여 무한 루프 &lt;b&gt;혹은&lt;/b&gt; 일정 시간 주기로 계속 파티클이 생성되게 설정&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 복습 질문&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Spawn Burst&lt;/b&gt;와 &lt;b&gt;Spawn Rate&lt;/b&gt;의 &lt;b&gt;차이점&lt;/b&gt;은 무엇일까요?&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Add Velocity&lt;/b&gt; 모듈은 &lt;b&gt;언제(얼마나 자주) 실행&lt;/b&gt;될까요?&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;힌트&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 스테이지(Spawn? Update?)에서 모듈이 실행되는지, 그 실행 빈도가 어떻게 다른지 떠올려 보세요.&lt;/li&gt;
&lt;li&gt;&amp;ldquo;Burst&amp;rdquo;는 순간적으로 한 번, &amp;ldquo;Rate&amp;rdquo;는 시간에 따라 여러 번이 핵심입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 **미리 정의된 템플릿(Simple Sprite Burst)**을 활용해 빠르게 파티클을 생성하고,&lt;br /&gt;&lt;b&gt;Add Velocity&lt;/b&gt;로 움직이는 파티클을 여러 개 만들어보았습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;템플릿 사용 장점&lt;/b&gt;: 반복 작업이 줄어들어 편리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Emitter State&lt;/b&gt; 조정: 파티클을 한 번만 스폰할지, 무한 반복할지 설정 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Spawn Rate&lt;/b&gt;와 &lt;b&gt;Add Velocity&lt;/b&gt;로 시각적 다이나믹 효과 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 강의에서는 이 과제에 대한 &lt;b&gt;정답&lt;/b&gt;을 확인하고, 더 다양한 모듈과 기능을 살펴볼 예정입니다.&lt;br /&gt;궁금하신 점이나 더 알아보고 싶은 부분이 있다면 언제든 댓글로 남겨주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즐거운 언리얼 VFX 작업 되시길 바랍니다!&lt;/b&gt;&lt;/p&gt;</description>
      <category>UnraealEngine/NiagaraSystem</category>
      <author>남찬우</author>
      <guid isPermaLink="true">https://kakaoncw.tistory.com/94</guid>
      <comments>https://kakaoncw.tistory.com/94#entry94comment</comments>
      <pubDate>Thu, 20 Mar 2025 22:45:53 +0900</pubDate>
    </item>
    <item>
      <title>나이아가라 준비</title>
      <link>https://kakaoncw.tistory.com/92</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 씬 밝기 조정 및 PostProcess 설정&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 씬 밝기 낮추기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;언리얼 엔진에서 씬이 너무 밝을 경우, &lt;b&gt;PostProcessVolume&lt;/b&gt;을 이용해 조정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;시각 이펙트 &amp;gt; PostProcessVolume&lt;/b&gt;을 레벨에 드래그해 배치합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) PostProcessVolume 설정&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Details&lt;/b&gt; 창에서 Infinite Extent(Unbound) 옵션을 활성화하여, 씬 전체에 PostProcess가 적용되도록 합니다.&lt;/li&gt;
&lt;li&gt;auto 로 검색하면 나오는 &lt;b&gt;Exposure&lt;/b&gt;(노출) 섹션에서 Metering Mode를 &lt;b&gt;Auto Exposure&lt;/b&gt;에서 &lt;b&gt;Manual&lt;/b&gt;로 변경합니다.&lt;/li&gt;
&lt;li&gt;노출이 크게 떨어지면, &lt;b&gt;Exposure Compensation&lt;/b&gt; 값을 원하는 밝기에 맞춰 조정합니다. (예: 12 정도)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;717&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVflko/btsMRj6jGPa/N2MkaVRkkjkKHTV2RAk2X0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVflko/btsMRj6jGPa/N2MkaVRkkjkKHTV2RAk2X0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVflko/btsMRj6jGPa/N2MkaVRkkjkKHTV2RAk2X0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVflko%2FbtsMRj6jGPa%2FN2MkaVRkkjkKHTV2RAk2X0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1007&quot; height=&quot;717&quot; data-origin-width=&quot;1007&quot; data-origin-height=&quot;717&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Tip&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수치를 조절해가며 씬 분위기에 맞는 밝기를 찾아보세요.&lt;/li&gt;
&lt;li&gt;PostProcessVolume은 한 씬 안에서 여러 개를 중첩해서도 사용할 수 있지만, Infinite Extent를 켜면 레벨 전체에 강력하게 적용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 데모 디스플레이 크기 조정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레벨 내 데모 룸 또는 디스플레이 액터(예: 플레인, 보드 등)를 선택해 &lt;b&gt;Transform&lt;/b&gt; 값을 조정합니다.&lt;/li&gt;
&lt;li&gt;예시: 너비(Width)를 7, 높이(Height)를 4.5 등으로 설정해 디스플레이를 좀 더 크게 표시.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;587&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c13B4W/btsMP7skr1L/P2rWBtWrFjRxkjZrYK7Wa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c13B4W/btsMP7skr1L/P2rWBtWrFjRxkjZrYK7Wa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c13B4W/btsMP7skr1L/P2rWBtWrFjRxkjZrYK7Wa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc13B4W%2FbtsMP7skr1L%2FP2rWBtWrFjRxkjZrYK7Wa0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;587&quot; height=&quot;450&quot; data-origin-width=&quot;587&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 나이아가라(Niagara) 개념 잡기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 나이아가라 시스템(Niagara System)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;나이아가라 시스템&lt;/b&gt;은 여러 이미터(Emitter)를 담는 &lt;b&gt;컨테이너&lt;/b&gt;입니다.&lt;/li&gt;
&lt;li&gt;월드(씬)에 직접 배치하거나 스폰(Spawn)할 수 있는 최상위 개념입니다.&lt;/li&gt;
&lt;li&gt;VFX를 만들 때, 시스템 하나에 여러 이미터를 담아 &lt;b&gt;복합적인 시각 이펙트&lt;/b&gt;를 연출할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 나이아가라 이미터(Niagara Emitter)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;나이아가라 이미터&lt;/b&gt;는 &lt;b&gt;파티클을 생성하고 제어&lt;/b&gt;하는 개별 단위입니다.&lt;/li&gt;
&lt;li&gt;하나의 시스템 안에 여러 이미터를 넣으면, 그만큼 다양한 파티클이 한 번에 생성됩니다.&lt;/li&gt;
&lt;li&gt;이미터 자체가 파티클은 아니며, &lt;b&gt;파티클을 만들어내는 &amp;lsquo;공장&amp;rsquo;&lt;/b&gt; 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 나이아가라 폴더 구조 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 수의 VFX와 관련 에셋을 만들 때는 폴더 구조가 중요합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;콘텐츠 브라우저&lt;/b&gt;에서 새 폴더를 생성합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: Niagara라는 폴더 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그 안에 또 다른 폴더를 만들어 &lt;b&gt;세부 카테고리&lt;/b&gt;를 나눕니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: NiagaraBasic &amp;rarr; 01-FirstEmitter 등으로 세분화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;생성되는 에셋(이미터, 시스템 등)은 &lt;b&gt;명명 규칙&lt;/b&gt;을 정해 일관성 있게 관리합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: NE_FirstEmitter &amp;rarr; &amp;ldquo;Niagara Emitter&amp;rdquo;의 앞 글자를 딴 NE_를 접두어로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 첫 번째 나이아가라 이미터 만들기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 빈 이미터 생성하기&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;콘텐츠 브라우저&lt;/b&gt;에서 우클릭 &amp;rarr; &lt;b&gt;FX&lt;/b&gt; 탭 &amp;rarr; &lt;b&gt;Niagara Emitter&lt;/b&gt; 선택&lt;/li&gt;
&lt;li&gt;팝업 창에서 제공되는 세 가지 옵션 중, &lt;b&gt;모듈이나 렌더러가 없는 &amp;lsquo;빈 이미터&amp;rsquo;&lt;/b&gt; 템플릿을 선택&lt;/li&gt;
&lt;li&gt;생성된 이미터에 이름을 지정합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: NE_FirstEmitter (Niagara Emitter의 약자 NE + 원하는 이름)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 나이아가라 에디터 기본 둘러보기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더블클릭하여 이미터 에셋을 열면, &lt;b&gt;나이아가라 에디터&lt;/b&gt;가 표시됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;좌측 프리뷰 창&lt;/b&gt;: 마우스 좌클릭 드래그로 회전, 가운데 버튼 드래그로 이동&lt;/li&gt;
&lt;li&gt;&lt;b&gt;중앙 패널&lt;/b&gt;: 이미터의 단계별 처리(스폰, 업데이트, 렌더 등)가 위에서 아래로 실행&lt;/li&gt;
&lt;li&gt;초기에는 아무 파티클도 없고, 렌더 옵션도 비어 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 스프라이트 렌더러 추가하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스프라이트(Sprite)&lt;/b&gt; 는 카메라를 항상 정면으로 바라보는 2D 평면입니다. 파티클을 표현하는 데 자주 쓰입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Niagara Emitter&lt;/b&gt; 에디터에서, 맨 아래 Render 섹션을 클릭합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;추가(Add Renderer)&lt;/b&gt; 버튼을 누르고 &lt;b&gt;Sprite Renderer&lt;/b&gt;를 선택합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Details&lt;/b&gt; 패널에서 머티리얼(Material) 슬롯 등이 표시됩니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 스프라이트 머티리얼이 할당되며, 필요하다면 &lt;b&gt;커스텀 머티리얼&lt;/b&gt;을 만들어 적용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;렌더러 종류&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Sprite Renderer&lt;/b&gt;: 2D 평면, 카메라 정면을 향함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Mesh Renderer&lt;/b&gt;: 메쉬 형태(3D 오브젝트)를 파티클로 사용.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Ribbon Renderer&lt;/b&gt;: 이동 경로를 리본 형태로 연결.&lt;/li&gt;
&lt;li&gt;기타 다른 렌더러는 특수한 경우에 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1853&quot; data-origin-height=&quot;733&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0lnKk/btsMSoyKFj1/psp6YyCbK8FCR6IYBVEmXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0lnKk/btsMSoyKFj1/psp6YyCbK8FCR6IYBVEmXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0lnKk/btsMSoyKFj1/psp6YyCbK8FCR6IYBVEmXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0lnKk%2FbtsMSoyKFj1%2Fpsp6YyCbK8FCR6IYBVEmXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1853&quot; height=&quot;733&quot; data-origin-width=&quot;1853&quot; data-origin-height=&quot;733&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 간단 실습 (도전 과제)&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;빈 나이아가라 이미터&lt;/b&gt;를 직접 생성해보세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;렌더 스테이지&lt;/b&gt;(맨 아래 섹션)에 스프라이트 렌더러를 추가합니다.&lt;/li&gt;
&lt;li&gt;에디터 뷰포트에서 마우스 조작을 익히고, 패널에 어떤 옵션들이 있는지 확인해봅니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 파티클이 보이지 않는 것은 정상입니다.&lt;br /&gt;스프라이트 렌더러만 추가했을 뿐, 파티클 스폰 로직을 넣지 않았기 때문입니다.&lt;br /&gt;또한 현재는 &lt;b&gt;이미터&lt;/b&gt;만 만든 것이므로, 레벨에 드래그 앤 드롭해서 배치할 수도 없습니다. (이 부분은 &amp;lsquo;나이아가라 시스템&amp;rsquo;으로 만들어야 가능)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리 및 다음 강의 예고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;씬 밝기 및 PostProcessVolume&lt;/b&gt; 조절&lt;/li&gt;
&lt;li&gt;&lt;b&gt;나이아가라 시스템과 이미터&lt;/b&gt; 개념 이해&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스프라이트 렌더러&lt;/b&gt; 추가 과정을 살펴봤습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 강의(글)에서는 나이아가라 이미터를 &lt;b&gt;실제 레벨에 배치&lt;/b&gt;하고, 파티클이 제대로 표시되도록 만드는 과정을 알아볼 예정입니다.&lt;br /&gt;차근차근 따라 오시면, 나만의 멋진 VFX를 구현할 수 있을 겁니다!&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추가 팁&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에디터 전체 설정(프로젝트 세팅, 에디터 퍼포먼스 옵션 등)도 미리 살펴보면, 이후 VFX 작업 시 더 수월해집니다.&lt;/li&gt;
&lt;li&gt;나이아가라에 익숙해지면, 여러 이미터를 결합해 불꽃, 폭발, 연기 등 다양한 효과를 동시에 연출해 보세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>UnraealEngine/NiagaraSystem</category>
      <author>남찬우</author>
      <guid isPermaLink="true">https://kakaoncw.tistory.com/92</guid>
      <comments>https://kakaoncw.tistory.com/92#entry92comment</comments>
      <pubDate>Thu, 20 Mar 2025 21:45:51 +0900</pubDate>
    </item>
    <item>
      <title>RPC</title>
      <link>https://kakaoncw.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Unreal Engine RPC 자세히 살펴보기&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. RPC 사용 예제 및 코드 샘플&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진에서 &lt;b&gt;RPC&lt;/b&gt;(Remote Procedure Call)는 네트워크로 연결된 다른 인스턴스에서 함수를 호출하는 기능입니다. 주로 멀티플레이어 게임에서 &lt;b&gt;클라이언트-서버&lt;/b&gt; 간 이벤트 전송에 사용되며, 클라이언트-&amp;gt;서버, 서버-&amp;gt;특정 클라이언트, 서버-&amp;gt;모든 클라이언트 형태로 동작하는 세 가지 RPC 유형이 있습니다 (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=%2A%20Run%20on%20Server%20,server%20instance%20of%20this%20Actor&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;) (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=3,Multicast%20will%20only%20execute%20locally&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;). 각 RPC의 사용법과 흐름을 살펴보고, &lt;b&gt;Reliable&lt;/b&gt;(신뢰)과 &lt;b&gt;Unreliable&lt;/b&gt;(비신뢰) 옵션의 차이도 알아보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Server RPC (클라이언트 -&amp;gt; 서버 호출)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Server RPC&lt;/b&gt;는 &lt;b&gt;클라이언트가 서버에서 실행되도록 함수를 호출&lt;/b&gt;하는 RPC입니다. 언리얼의 클라이언트-서버 구조에서 &lt;b&gt;클라이언트가 서버 측 함수를 직접 실행시키는 유일한 방법&lt;/b&gt;입니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=Server%20RPC%20%EA%B0%9C%EC%9A%94&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 예를 들어 플레이어가 총을 쏘거나 문을 여는 입력을 하면, 클라이언트가 Server RPC를 호출하여 그 동작을 서버에서 처리합니다. Server RPC는 주로 &lt;b&gt;게임 플레이 권한이 서버에 있으므로&lt;/b&gt; 중요한 게임 로직(피해 계산, 아이템 생성 등)을 서버에서 실행할 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 예시:&lt;/b&gt; 클라이언트가 서버에 폭탄을 생성하도록 요청하는 함수:&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// 헤더 파일 (.h)
UFUNCTION(Server, Reliable, WithValidation)
void ServerSpawnBomb(FVector SpawnLocation);

// CPP 파일 (.cpp)
bool AMyCharacter::ServerSpawnBomb_Validate(FVector SpawnLocation) {
    // 서버에서 수신한 요청 검증 (예: 좌표가 유효한지 확인)
    return SpawnLocation.Z &amp;gt;= 0.0f;
}

void AMyCharacter::ServerSpawnBomb_Implementation(FVector SpawnLocation) {
    // 이 코드는 서버에서 실행됩니다. 폭탄 액터를 생성
    GetWorld()-&amp;gt;SpawnActor&amp;lt;ABombActor&amp;gt;(BombClass, SpawnLocation, FRotator::ZeroRotator);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 UFUNCTION(Server, Reliable, WithValidation)으로 선언된 함수는 &lt;b&gt;클라이언트에서 호출&lt;/b&gt;하면 엔진이 자동으로 &lt;b&gt;서버에서 _Implementation 함수&lt;/b&gt;를 실행합니다. WithValidation을 사용하면 _Validate 함수를 구현하여 서버에서 인자 등을 검증할 수 있습니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=,Connection%EC%9D%84%20%EC%86%8C%EC%9C%A0%ED%95%9C%20Actor%EB%A5%BC%20%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC%20%ED%95%A8&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;) (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=Reliable%20,%ED%98%B8%EC%B6%9C%ED%95%9C%20%EA%B2%B0%EA%B3%BC%EA%B0%80%20%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C%20%EA%B5%AC%EB%8F%99%EB%90%98%EA%B8%B0%20%EB%95%8C%EB%AC%B8%EC%9D%B4%EB%8B%A4&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 클라이언트는 ServerSpawnBomb()을 호출하기만 하면 되고, 실제 폭탄 생성 로직은 서버의 _Implementation에서 처리됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 흐름:&lt;/b&gt; 클라이언트가 ServerSpawnBomb 호출 &amp;rarr; 엔진이 RPC를 네트워크로 전송 &amp;rarr; 서버가 RPC 수신 후 _Validate로 유효성 검사 &amp;rarr; _Implementation 실행으로 폭탄 생성 &amp;rarr; 결과가 서버 authoritative하게 적용.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 시기:&lt;/b&gt; 클라이언트 입력이나 상호작용을 서버에서 처리해야 할 때 사용합니다. 예를 들어 공격 시도, 문 열기, 채팅 전송 등 &lt;b&gt;클라이언트가 요청하고 서버가 결정해야 하는 행동&lt;/b&gt;에 Server RPC를 활용합니다. 단, &lt;b&gt;Server RPC는 해당 액터의 소유주(owner)인 클라이언트만 호출할 수 있으며&lt;/b&gt; 소유하지 않은 액터에서 호출하면 무시됩니다 (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=2,that%20Actor%20that%20is%20relevant&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;) (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=Actor%20Ownership%20Not%20Replicated%20NetMulticast,Dropped%20Runs%20on%20Invoking%20Client&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;). 또한 로컬(리스닝) 서버의 플레이어는 자신이 서버이면서 클라이언트이므로 Server RPC를 호출할 수 있습니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=%EA%B0%81%C2%A0%20RPC%20%EC%A2%85%EB%A5%98%EB%A7%88%EB%8B%A4%20%EC%98%AC%EB%B0%94%EB%A5%B4%EA%B2%8C%20%EC%82%AC%EC%9A%A9%ED%95%A0,%EA%B2%83&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;).&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Client RPC (서버 -&amp;gt; 특정 클라이언트 호출)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Client RPC&lt;/b&gt;는 &lt;b&gt;서버가 특정 클라이언트에서 함수를 실행&lt;/b&gt;하도록 호출하는 RPC입니다. UFUNCTION(Client, ...)로 선언하며, 주로 &lt;b&gt;서버가 한 플레이어에게만 알려줘야 하는 이벤트&lt;/b&gt;나 UI 업데이트 등에 사용됩니다. &lt;b&gt;호출은 서버에서만 가능&lt;/b&gt;하고, 해당 액터를 소유한 &lt;b&gt;클라이언트에서만&lt;/b&gt; _Implementation이 실행됩니다 (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=2,executed%20on%20the%20server%2C%20the&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;) (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=Actor%20Ownership%20Not%20Replicated%20NetMulticast,on%20Server%20Runs%20on%20Server&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;). 예를 들어, 서버가 한 플레이어의 화면에만 피격 마커를 표시하거나 개인 메시지를 보내는 경우 Client RPC를 이용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 예시:&lt;/b&gt; 서버가 플레이어에게 피격 표시를 보내는 함수:&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// 헤더 (.h)
UFUNCTION(Client, Unreliable)
void ClientShowHitMarker();

// CPP (.cpp)
void AMyPlayerController::ClientShowHitMarker_Implementation() {
    // 이 코드는 특정 클라이언트(플레이어)의 머신에서 실행됨
    if (HUDWidget) {
        HUDWidget-&amp;gt;ShowHitMarker();  // 피격 마커 UI 표시
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 필요한 순간에 PlayerController-&amp;gt;ClientShowHitMarker()를 호출합니다. 그러면 엔진이 해당 플레이어의 클라이언트로 RPC를 보내고, 그 클라이언트에서 _Implementation이 실행되어 HUD에 마커가 표시됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 흐름:&lt;/b&gt; 서버에서 ClientShowHitMarker 호출 &amp;rarr; RPC가 해당 플레이어 클라이언트로 전송 &amp;rarr; 클라이언트에서 _Implementation 실행. 다른 클라이언트에는 영향 없음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 시기:&lt;/b&gt; 한 플레이어만을 위한 피드백이나 데이터 전송에 사용합니다. 대표적으로 &lt;b&gt;HUD/UI 업데이트&lt;/b&gt;, 개인 알림, 특정 플레이어만이 알아야 하는 이벤트 등이 있습니다. Client RPC는 &lt;b&gt;액터의 소유주인 클라이언트에게만 전달&lt;/b&gt;되므로, 보통 PlayerController, PlayerState, Pawn 같이 &lt;b&gt;플레이어 소유 Actors&lt;/b&gt;에서 사용합니다 (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=2,executed%20on%20the%20server%2C%20the&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;). 서버가 아닌 곳(클라이언트)에서 이 함수를 호출하면 로컬에서만 실행되고 네트워크 전송은 일어나지 않습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NetMulticast RPC (서버 -&amp;gt; 모든 클라이언트 브로드캐스트)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NetMulticast RPC&lt;/b&gt;는 &lt;b&gt;서버를 포함한 모든 클라이언트에서 함수를 실행&lt;/b&gt;하는 RPC입니다. UFUNCTION(NetMulticast, ...)로 선언하며, &lt;b&gt;서버에서만 호출&lt;/b&gt;해야 합니다. 서버에서 NetMulticast RPC를 호출하면 &lt;b&gt;서버 자체에서 로컬 실행된 후, 연결된 모든 클라이언트에 RPC가 전송&lt;/b&gt;되어 각 클라이언트에서 _Implementation이 실행됩니다 (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=,Multicast%20will%20only%20execute%20locally&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;) (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=,IsLocallyControlled%20%ED%95%A8%EC%88%98&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 이를 통해 &lt;b&gt;브로드캐스트 이벤트&lt;/b&gt;를 구현할 수 있습니다. 예를 들어 폭발 이펙트나 사운드를 모든 플레이어에게 재생시키는 경우, 또는 경기 종료 이벤트 등을 전체에 알릴 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 예시:&lt;/b&gt; 폭발 배럴(Actor)이 폭발하여 모든 클라이언트에 이펙트를 재생시키는 함수:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// 헤더 (.h)
UFUNCTION(NetMulticast, Reliable)
void MulticastPlayExplosionFX(FVector Location);

// CPP (.cpp)
void AExplosiveBarrel::MulticastPlayExplosionFX_Implementation(FVector Location) {
    // 서버와 모든 클라이언트에서 실행됨: 폭발 이펙트/사운드 재생
    UGameplayStatics::SpawnEmitterAtLocation(GetWorld(), ExplosionFX, Location);
    UGameplayStatics::PlaySoundAtLocation(GetWorld(), ExplosionSound, Location);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서 Barrel-&amp;gt;MulticastPlayExplosionFX(ImpactPoint)를 호출하면, 즉시 서버에서도 _Implementation이 실행되어 폭발 이펙트를 띄우고, 엔진이 모든 클라이언트에 RPC를 보내 각자 똑같이 이펙트를 재생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 흐름:&lt;/b&gt; 서버에서 MulticastPlayExplosionFX 호출 &amp;rarr; 서버에서 _Implementation 로컬 실행 &amp;rarr; 모든 연결된 클라이언트로 RPC 전송 &amp;rarr; 각 클라이언트에서 _Implementation 실행. (클라이언트가 이 RPC를 호출하면 자기 자신에게만 실행되고 다른 곳에는 전파되지 않으므로 &lt;b&gt;항상 서버에서 호출&lt;/b&gt;해야 합니다 (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=,Multicast%20will%20only%20execute%20locally&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;) (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=NetMulticast%EB%A5%BC%20%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8%EC%97%90%EC%84%9C%20%ED%98%B8%EC%B6%9C%ED%95%98%EB%A9%B4%20%EB%B0%94%EB%B3%B4%21&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;).)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용 시기:&lt;/b&gt; &lt;b&gt;모든 플레이어에게 동시 전파해야 하는 연출이나 이벤트&lt;/b&gt;에 사용합니다. 예를 들어 &lt;b&gt;폭발, 총격 효과, 날씨 변화, 게임 단계 변경&lt;/b&gt; 등을 들 수 있습니다. NetMulticast RPC는 내부적으로 &lt;b&gt;액터의 relevancy(연관성)&lt;/b&gt; 규칙을 따르는데, 해당 액터가 특정 클라이언트에 &lt;b&gt;Relevant(관계 있음)&lt;/b&gt; 해야 RPC도 전송됩니다 (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=,the%20server%20or%20other%20clients&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;). 따라서 &lt;b&gt;액터가 어느 클라이언트에도 비연관(invisible)&lt;/b&gt; 상태라면 그 클라이언트에는 RPC가 전달되지 않습니다. 일반적으로 모든 플레이어에게 확실히 전달하려면 bAlwaysRelevant = true 같은 설정을 고려하거나, 모든 플레이어가 항상 소유하는 Actor(예: GameState 등)에서 호출합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Reliable vs Unreliable RPC 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RPC 함수에는 &lt;b&gt;Reliable&lt;/b&gt;(신뢰성)과 &lt;b&gt;Unreliable&lt;/b&gt;(비신뢰성) 옵션을 지정할 수 있습니다. 이 옵션은 네트워크 전송의 보장 여부와 성능에 영향을 줍니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=Unreliable%20%20,%ED%98%B8%EC%B6%9C%ED%95%9C%20%EA%B2%B0%EA%B3%BC%EA%B0%80%20%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C%20%EA%B5%AC%EB%8F%99%EB%90%98%EA%B8%B0%20%EB%95%8C%EB%AC%B8%EC%9D%B4%EB%8B%A4&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;).&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Unreliable RPC&lt;/b&gt;: 패킷 유실 시 재전송을 보장하지 &lt;b&gt;않는&lt;/b&gt; 방식입니다. 네트워크 상황에 따라 해당 RPC 호출이 도착하지 않을 수 있지만, 그만큼 &lt;b&gt;오버헤드가 낮고 전송이 빠릅니다&lt;/b&gt; (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=Unreliable%20%20,%ED%98%B8%EC%B6%9C%ED%95%9C%20%EA%B2%B0%EA%B3%BC%EA%B0%80%20%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C%20%EA%B5%AC%EB%8F%99%EB%90%98%EA%B8%B0%20%EB%95%8C%EB%AC%B8%EC%9D%B4%EB%8B%A4&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 따라서 &lt;b&gt;자주 발생하는 업데이트&lt;/b&gt;에 적합합니다. 예를 들어 &lt;b&gt;캐릭터 움직임&lt;/b&gt;처럼 빈번히 전송되어 일부 프레임의 업데이트가 누락되어도 큰 문제가 없는 데이터에 사용합니다 (실제로 언리얼의 기본 움직임 복제도 불필요한 트래픽을 줄이기 위해 Unreliable로 처리됩니다). Unreliable RPC는 &lt;b&gt;&amp;ldquo;fire-and-forget&amp;rdquo;&lt;/b&gt; 방식으로, 네트워크 상황이 나쁘면 해당 호출을 드롭하여 버퍼 오버플로를 방지합니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/difference-between-property-replication-and-rpc-technically/755737#:~:text=RPCs%20are%20sent%20when%20you,so%20it%20only%20executes%20once&quot;&gt;Difference between Property Replication and RPC technically - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Reliable RPC&lt;/b&gt;: 패킷이 목적지에 도달할 때까지 &lt;b&gt;재시도하며 전송을 보장&lt;/b&gt;합니다. 반드시 실행되어야 할 중요한 이벤트에 사용합니다. 다만 &lt;b&gt;매 호출마다 확인/재전송 절차가 있어 성능 부하&lt;/b&gt;가 높을 수 있고, 네트워크가 느릴 때는 재시도로 인해 지연이 커질 수 있습니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=Reliable%20,%ED%98%B8%EC%B6%9C%ED%95%9C%20%EA%B2%B0%EA%B3%BC%EA%B0%80%20%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C%20%EA%B5%AC%EB%8F%99%EB%90%98%EA%B8%B0%20%EB%95%8C%EB%AC%B8%EC%9D%B4%EB%8B%A4&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). &lt;b&gt;순서 보장&lt;/b&gt;도 되기 때문에, 중요한 사건(예: 피해 적용, 사망 처리, 아이템 획득 등)은 Reliable로 보내 안정성을 확보합니다 (&lt;a href=&quot;https://www.reddit.com/r/Battalion1944/comments/844s5a/our_lead_programmer_james_tatum_explaining/#:~:text=,a%20bit%20of%20an%20issue&quot;&gt;Our lead programmer James Tatum explaining 'Outgoing Reliable Buffer Overflow' from March 8th update! : r/Battalion1944&lt;/a&gt;). 단, &lt;b&gt;과도한 Reliable 사용은 네트워크 버퍼를 꽉 채워 &amp;ldquo;Reliable Buffer Overflow&amp;rdquo; 오류&lt;/b&gt;를 야기할 수 있으므로 꼭 필요한 경우에만 사용해야 합니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=Reliable%20,%ED%98%B8%EC%B6%9C%ED%95%9C%20%EA%B2%B0%EA%B3%BC%EA%B0%80%20%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C%20%EA%B5%AC%EB%8F%99%EB%90%98%EA%B8%B0%20%EB%95%8C%EB%AC%B8%EC%9D%B4%EB%8B%A4&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;) (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=server.%20,them%20in%20response%20to%20gameplay&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에서는 UFUNCTION 매크로에 &lt;b&gt;Reliable 또는 Unreliable 키워드&lt;/b&gt;로 지정합니다. 지정하지 않으면 기본적으로 Reliable로 처리됩니다. 예시로, 잦은 총알 발사 트레이스는 Unreliable로 보내고, 플레이어 사망 RPC는 Reliable로 보내는 식입니다:&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;UFUNCTION(Server, Unreliable)
void ServerUpdateAimDirection(FVector NewDir);  // 에임 방향 빈번 전송 -&amp;gt; Unreliable

UFUNCTION(NetMulticast, Reliable)
void MulticastPlayerDied();  // 플레이어 사망 이벤트 -&amp;gt; Reliable
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 상황에 맞게 선택합니다. &lt;b&gt;특히 틱(Tick)마다 호출되거나 입력 연타로 매우 빈번한 RPC는 반드시 Unreliable&lt;/b&gt;로 해야 합니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=server.%20,them%20in%20response%20to%20gameplay&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 반대로 게임 진행에 &lt;b&gt;중요한 이벤트는 Reliable로 한정&lt;/b&gt;하여, 불필요한 트래픽 증가를 막고 필요한 보장은 얻는 것이 베스트 프랙티스입니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=Reliable%20,%ED%98%B8%EC%B6%9C%ED%95%9C%20%EA%B2%B0%EA%B3%BC%EA%B0%80%20%EC%84%9C%EB%B2%84%EC%97%90%EC%84%9C%20%EA%B5%AC%EB%8F%99%EB%90%98%EA%B8%B0%20%EB%95%8C%EB%AC%B8%EC%9D%B4%EB%8B%A4&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;).&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 상태 복제(Replication)와 RPC의 관계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진 네트워크에서는 **변수 복제(Replicated Properties)**와 &lt;b&gt;RPC&lt;/b&gt; 두 가지 방식으로 상태를 동기화합니다. &lt;b&gt;변수 복제&lt;/b&gt;는 서버가 가지고 있는 변수 값을 자동으로 클라이언트에 동기화시키는 시스템이고, &lt;b&gt;RPC&lt;/b&gt;는 함수를 호출하여 특정 시점에 이벤트를 전달하는 방식입니다. 두 방법 모두 서버-클라이언트 간 데이터를 전달하지만, &lt;b&gt;동작 방식과 용도에 차이&lt;/b&gt;가 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Replicated 변수 vs RPC &amp;ndash; 차이점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 전달 방향:&lt;/b&gt; Replicated 변수는 &lt;b&gt;오직 서버 -&amp;gt; 클라이언트 일방향&lt;/b&gt;으로만 동기화됩니다. 서버에서 값이 변경되면 엔진이 변경된 값을 관련된 클라이언트들에게 보냅니다. 클라이언트에서 서버로 &lt;b&gt;상태를 보내는 기능은 없으며&lt;/b&gt;, 클라이언트의 정보는 서버로 직접 복제되지 않습니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/difference-between-property-replication-and-rpc-technically/755737#:~:text=One%20key%20difference%20between%20RPCs,clients%2C%20never%20the%20other%20way&quot;&gt;Difference between Property Replication and RPC technically - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). 반면 RPC는 종류에 따라 &lt;b&gt;양방향 통신&lt;/b&gt;이 가능합니다. &lt;b&gt;Server RPC&lt;/b&gt;를 통해 클라이언트-&amp;gt;서버로 호출하거나, &lt;b&gt;Client RPC&lt;/b&gt;로 서버-&amp;gt;특정 클라이언트, &lt;b&gt;Multicast RPC&lt;/b&gt;로 서버-&amp;gt;모두 전송이 가능합니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/difference-between-property-replication-and-rpc-technically/755737#:~:text=One%20key%20difference%20between%20RPCs,clients%2C%20never%20the%20other%20way&quot;&gt;Difference between Property Replication and RPC technically - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). 즉, &lt;b&gt;클라이언트가 서버에 정보를 전달하려면 RPC가 필수&lt;/b&gt;이며, 단순 복제만으로는 할 수 없습니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=%EC%96%B4%EB%8A%90%20%EC%8B%9C%EC%A0%90%EC%97%90%20%EB%B0%94%EB%80%8C%EB%8A%94%EC%A7%80%20%EC%95%8C%EC%A7%80%20%EB%AA%BB%ED%95%98%EC%A7%80%EB%A7%8C,%EB%B3%B5%EC%A0%9C%EB%B3%B8%EC%97%90%EC%84%9C%20%EC%9B%90%EB%B3%B8%EC%9C%BC%EB%A1%9C%20%EB%82%A0%EB%A6%B4%20%EC%88%98%20%EC%97%86%EB%8B%A4&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전송 타이밍 및 제어:&lt;/b&gt; Replicated 속성은 &lt;b&gt;엔진이 내부 스케줄링&lt;/b&gt;에 따라 전송합니다. 한 프레임에 여러 번 값이 바뀌어도 네트워크 업데이트 주기에 맞춰 &lt;b&gt;가장 최근 값만 보내거나 중간 값들을 생략&lt;/b&gt;할 수 있습니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/difference-between-property-replication-and-rpc-technically/755737#:~:text=Replicated%20variables%20are%20occasionally%20sent,between&quot;&gt;Difference between Property Replication and RPC technically - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). 즉, &lt;b&gt;언제 전달될지 정확히 제어하기 어렵고&lt;/b&gt;, 빈번한 변경 시 모든 변경이 다 전달되지 않을 수 있습니다 (예: 서버에서 변수 값을 3번 바꾸면, 클라이언트는 마지막 값만 받고 중간 변경은 스킵될 수 있음 (&lt;a href=&quot;https://forums.unrealengine.com/t/difference-between-property-replication-and-rpc-technically/755737#:~:text=Replicated%20variables%20are%20occasionally%20sent,between&quot;&gt;Difference between Property Replication and RPC technically - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;)). 반면 RPC는 함수를 호출하는 순간 네트워크 전송을 &lt;b&gt;즉시 요청&lt;/b&gt;합니다. 실시간으로 호출 시점이 보장되며, &lt;b&gt;Reliable RPC의 경우 호출 순서도 유지&lt;/b&gt;됩니다 (&lt;a href=&quot;https://www.reddit.com/r/Battalion1944/comments/844s5a/our_lead_programmer_james_tatum_explaining/#:~:text=,a%20bit%20of%20an%20issue&quot;&gt;Our lead programmer James Tatum explaining 'Outgoing Reliable Buffer Overflow' from March 8th update! : r/Battalion1944&lt;/a&gt;). 다만 내부적으로도 네트워크 프레임에 따라 큐잉되지만, 의도적으로 지연시키는 경우는 없고 호출한 순서대로 패킷이 만들어집니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/difference-between-property-replication-and-rpc-technically/755737#:~:text=RPCs%20are%20sent%20when%20you,so%20it%20only%20executes%20once&quot;&gt;Difference between Property Replication and RPC technically - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). &lt;b&gt;정리하면&lt;/b&gt;: Replication은 &lt;b&gt;지속적인 상태 동기화&lt;/b&gt;에 적합하고, RPC는 &lt;b&gt;특정 이벤트 트리거&lt;/b&gt;에 적합합니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=Replicated%20Replicated%20Notify%20RPC%20%EB%8B%A8%EC%88%9C,OnRep_OOO%29%EB%A5%BC%20%ED%98%B8%EC%B6%9C%ED%95%98%EB%8A%94&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지속성 및 새로운 클라이언트:&lt;/b&gt; 복제된 변수는 &lt;b&gt;상태 그 자체&lt;/b&gt;이기 때문에, 나중에 클라이언트가 새로 접속하거나 지연되더라도 &lt;b&gt;최신 상태값을 받아 동기화&lt;/b&gt;할 수 있습니다. 반면 RPC는 &lt;b&gt;호출 당시 연결된 클라이언트에게만 전달되는 1회성 이벤트&lt;/b&gt;입니다. RPC로 어떤 사건을 보냈을 때 그 시점에 없던 신규 클라이언트는 그 이벤트를 놓치게 되지만, 변수 복제는 합류 시 현재 상태를 자동 동기화합니다. 예를 들어 문이 열린 상태를 표현할 때, &lt;b&gt;변수 복제&lt;/b&gt;로 문이 열린 여부를 bIsOpen으로 동기화하면 늦게 들어온 플레이어도 문이 열린 상태를 바로 알 수 있지만, &lt;b&gt;RPC로 문 열기 함수를 브로드캐스트&lt;/b&gt;했다면 나중에 들어온 플레이어는 문이 열린 걸 모를 수 있습니다. 따라서 &lt;b&gt;영속적인 상태는 변수 복제로 관리&lt;/b&gt;하고, &lt;b&gt;즉각적인 반응이나 이팩트는 RPC로 전파&lt;/b&gt;하는 식으로 역할을 나눕니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=,RPC%EB%B3%B4%EB%8B%A4%20Property%20Replication%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%A0%20%EA%B2%83&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;엔진 최적화:&lt;/b&gt; 언리얼의 변수 복제는 &lt;b&gt;Dirty flag와 Diff 기반 최적화&lt;/b&gt;가 적용됩니다. 값이 변하지 않으면 보내지 않으며, 변해도 이전 값과의 차이를 이용해 전송 크기를 줄이는 등의 최적화가 있습니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/difference-between-property-replication-and-rpc-technically/755737#:~:text=1,as%20a%20single%20byte%20instead&quot;&gt;Difference between Property Replication and RPC technically - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). 예를 들어, 플레이어 점수가 계속 증가하는 상황에서 전체 점수를 RPC로 매번 4바이트씩 보내는 대신, 복제 시스템은 증가량 1만 보낼 수도 있습니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/difference-between-property-replication-and-rpc-technically/755737#:~:text=1,as%20a%20single%20byte%20instead&quot;&gt;Difference between Property Replication and RPC technically - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). RPC는 매 호출시 전달해야 하는 모든 데이터를 패킷에 실어 보내므로, 빈번한 작은 변화에는 비효율적일 수 있습니다. 또한 RPC는 Reliable일 경우 매번 ACK를 기다리는 오버헤드가 있습니다. 반면 복제는 &lt;b&gt;Actor의 우선순위와 네트워크 예산에 따라&lt;/b&gt; 효율적으로 분배됩니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/difference-between-property-replication-and-rpc-technically/755737#:~:text=The%20amount%20of%20network%20bandwidth,when%2C%20on%20a%20prioritized%20schedule&quot;&gt;Difference between Property Replication and RPC technically - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). 따라서 &lt;b&gt;빈번하고 연속적인 상태 변화&lt;/b&gt;는 RPC 연쇄 호출보다 &lt;b&gt;변수 복제&lt;/b&gt;로 처리하는 것이 효율적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;복제 vs RPC &amp;ndash; 사용 기준과 예제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;언제 변수 복제를 쓰고, 언제 RPC를 쓸지&lt;/b&gt;는 해당 데이터의 성격에 따라 결정합니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=,RPC%EB%B3%B4%EB%8B%A4%20Property%20Replication%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%A0%20%EA%B2%83&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;):&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;게임플레이 핵심 상태&lt;/b&gt;(위치, 체력, 탄약수, 문 열린 여부 등): &lt;b&gt;변수 복제&lt;/b&gt;가 적합합니다. 이런 상태는 모든 클라이언트가 동기화해야 하고, 새로운 플레이어도 알아야 합니다. 서버에서 UPROPERTY(Replicated)로 선언하고 값 변경 시 클라이언트 OnRep 콜백을 사용하면, &lt;b&gt;안정적으로 모든 클라이언트에 전파&lt;/b&gt;됩니다. 예를 들어 플레이어 체력, 아이템 소유 여부, 게임 시간 등은 복제로 관리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일회성 이벤트나 임시 연출&lt;/b&gt;(폭발 효과, 사운드 재생, 채팅 메시지 등): &lt;b&gt;RPC&lt;/b&gt;가 적합합니다. 이런 이벤트는 발생 당시의 &lt;b&gt;즉각적인 반응&lt;/b&gt;이 중요하며, 이후의 지속적인 동기화는 필요 없습니다. 예를 들어 폭발 이펙트는 발생 순간에만 모든 플레이어에게 알려주면 되고, 나중에 접속한 플레이어가 그 순간의 이펙트를 다시 볼 필요는 없습니다. 이런 경우 서버에서 NetMulticast RPC로 브로드캐스트하면 효율적입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결합 사용:&lt;/b&gt; 영속 상태 변화와 동시에 즉각 효과가 필요할 때는 &lt;b&gt;복제와 RPC/OnRep의 조합&lt;/b&gt;을 씁니다. 예를 들어 문이 열리는 상황을 생각해보면, **문 열림 상태(bIsOpen)**는 Replicated로 두고, 시각적인 열리는 애니메이션은 &lt;b&gt;OnRep 함수&lt;/b&gt;나 RPC로 처리할 수 있습니다. 한 가지 방법은 서버에서 bIsOpen = true로 설정하면 클라이언트의 OnRep_IsOpen()에서 문 열기 애니메이션을 재생하게 하는 것입니다. 또는 상태 변수 복제와 동시에 MulticastPlayOpenEffect() RPC를 보내 현재 접속 중인 클라이언트들은 바로 효과를 보고, 추후 합류자는 bIsOpen 복제로 열린 상태만 동기화받게 할 수도 있습니다. &lt;b&gt;단&lt;/b&gt;, 복제와 RPC를 함께 사용할 때는 &lt;b&gt;타이밍에 주의&lt;/b&gt;해야 합니다. 같은 프레임에 새로 생성된 Actor를 RPC로 즉시 접근하면 해당 Actor가 아직 클라이언트에 생성되기 전일 수 있습니다. 예를 들어 총을 스폰한 직후 그 총으로 이펙트를 내는 RPC를 바로 보내면, 클라이언트에는 총이 아직 없어서 문제가 발생합니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=Replicated%EC%99%80%20%EA%B0%99%EC%9D%80%20Tick%EC%97%90%20RPC%EB%A5%BC%20%EB%82%A0%EB%A6%AC%EB%A9%B4,%EC%95%88%20%EB%90%9C%EB%8B%A4&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 이런 경우 RPC 대신 **RepNotify(OnRep)**를 사용하여 &lt;b&gt;Actor가 클라이언트에 생성된 후&lt;/b&gt;에 이펙트를 실행하도록 해야 안전합니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=ex,%EB%AC%B8%EC%A0%9C%EB%A5%BC%20%EC%B0%BE%EA%B8%B0%EA%B0%80%20%ED%9E%98%EB%93%A4%EB%8B%A4&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예제:&lt;/b&gt; 플레이어 캐릭터가 아이템을 획득하여 &lt;b&gt;버프 상태&lt;/b&gt;가 되는 경우를 생각해봅시다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;복제 사용:&lt;/b&gt; 캐릭터에 UPROPERTY(ReplicatedUsing=OnRep_PowerUpActive) bool bPowerUpActive; 변수를 두고, 서버가 아이템 획득 시 bPowerUpActive = true로 설정합니다. 그러면 엔진이 이를 모든 클라이언트에 복제하고, 각 클라이언트의 OnRep_PowerUpActive()가 호출되어 버프 발동 이펙트를 표시하게 할 수 있습니다. 새로운 플레이어가 들어와도 해당 캐릭터가 버프 중이라면 bPowerUpActive의 true 상태를 즉시 전달받습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RPC 사용:&lt;/b&gt; 대신 &lt;b&gt;즉각적인 피드백&lt;/b&gt;을 강조한다면, 서버에서 해당 캐릭터의 MulticastActivatePowerUp() RPC를 호출하여 전 클라이언트에서 버프 이펙트를 재생시킬 수 있습니다. 이 경우 역시 이펙트는 실시간으로 보이지만, 나중에 접속한 클라이언트는 이 캐릭터가 버프 상태인지 모르므로 &lt;b&gt;별도로 상태를 처리&lt;/b&gt;해야 합니다. 그러므로 &lt;b&gt;RPC만 사용할 경우&lt;/b&gt;, 버프 상태 자체도 변수로 복제하거나 상태 요청을 해야 합니다. 일반적으로 &lt;b&gt;상태 그 자체는 복제하고, 시각적 처리만 RPC/OnRep로 처리&lt;/b&gt;하는 패턴이 많이 쓰입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결론적으로&lt;/b&gt;, &lt;b&gt;상태 복제와 RPC는 상호보완적&lt;/b&gt;입니다. &amp;ldquo;게임의 지속 상태와 중요한 수치는 복제로, 이벤트와 연출은 RPC로&amp;rdquo; 라는 원칙이 많이 적용됩니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=,RPC%EB%B3%B4%EB%8B%A4%20Property%20Replication%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%A0%20%EA%B2%83&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 또한 &lt;b&gt;클라이언트-&amp;gt;서버로 데이터를 보내야 할 땐 반드시 RPC&lt;/b&gt;를 사용해야 한다는 점도 기억해야 합니다. 적절한 방법을 선택함으로써 네트워크 트래픽을 최적화하고 동기화 문제를 줄일 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. RPC 트러블슈팅 및 디버깅 기법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 RPC를 사용하다 보면 &lt;b&gt;함수가 호출되지 않거나 동작하지 않는 문제&lt;/b&gt;를 겪을 수 있습니다. 또한 성능 이슈나 예기치 않은 동작을 디버깅해야 할 때가 있습니다. 이번 섹션에서는 &lt;b&gt;RPC 관련 문제 상황의 원인 분석&lt;/b&gt;, 이를 해결하기 위한 &lt;b&gt;디버깅 도구 활용법&lt;/b&gt;, 그리고 &lt;b&gt;성능 최적화&lt;/b&gt;와 모범 사례를 정리합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RPC 호출이 이루어지지 않을 때 &amp;ndash; 원인 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RPC 함수를 호출했는데 서버/클라이언트에서 실행되지 않는다면 다음과 같은 원인을 의심할 수 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;액터의 Replication 설정 누락:&lt;/b&gt; RPC는 &lt;b&gt;Replication이 활성화된 Actor 또는 컴포넌트&lt;/b&gt;에서만 동작합니다 (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=1,Multicast%20RPCs%20are%20an%20exception&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;). bReplicates = true를 설정하지 않거나, 해당 Actor가 월드에 Spawn될 때 SetReplicates(true)를 하지 않았다면 RPC 호출이 네트워크로 전달되지 않습니다. 따라서 RPC가 안 될 때 가장 먼저 &lt;b&gt;해당 Actor가 복제 중인지(bReplicates)&lt;/b&gt; 확인해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;소유권(Owership) 문제:&lt;/b&gt; 클라이언트에서 Server RPC를 호출하려면, &lt;b&gt;그 Actor의 Owner가 해당 클라이언트&lt;/b&gt;여야 합니다 (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=2,Multicast%20RPCs%20are%20an%20exception&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;). 예를 들어 다른 플레이어가 소유한 Actor나 서버 전용 Actor(GameMode 등)에서 클라이언트가 Server RPC를 호출하면 엔진이 이를 &lt;b&gt;드롭(drop)&lt;/b&gt; 시킵니다 (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=Actor%20Ownership%20Not%20Replicated%20NetMulticast,Dropped%20Runs%20on%20Invoking%20Client&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;) (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=Server%20Runs%20on%20invoking%20Client,invoking%20Client%20Dropped%20Runs%20on&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;). 마찬가지로 서버에서 Client RPC를 호출할 때도 대상 클라이언트가 그 Actor를 소유하고 있어야 합니다 (일반적으로 PlayerController나 Pawn). 따라서 RPC가 무시될 경우 &lt;b&gt;Actor의 Owner와 호출 주체 관계&lt;/b&gt;를 점검해야 합니다. 올바른 소유자에서 호출했는지, 또는 SetOwner() 호출을 깜빡하지 않았는지 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;호출 컨텍스트 오류:&lt;/b&gt; RPC 함수는 &lt;b&gt;정해진 권한에서만 호출&lt;/b&gt;되어야 전송됩니다. &lt;b&gt;Server RPC&lt;/b&gt;는 클라이언트 측에서 호출해야 하고 (리스닝 서버의 로컬 플레이어는 예외적으로 호출 가능), &lt;b&gt;Client/Multicast RPC&lt;/b&gt;는 서버 측에서만 호출해야 합니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=%EA%B0%81%C2%A0%20RPC%20%EC%A2%85%EB%A5%98%EB%A7%88%EB%8B%A4%20%EC%98%AC%EB%B0%94%EB%A5%B4%EA%B2%8C%20%EC%82%AC%EC%9A%A9%ED%95%A0,%EA%B2%83&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 만약 규칙에 어긋나는 컨텍스트에서 호출하면 로컬로만 실행되거나 무시됩니다. 예를 들어, 클라이언트가 NetMulticast 함수를 호출하면 자기에게만 실행되고 다른 클라이언트나 서버에는 보내지지 않습니다 (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=,the%20server%20or%20other%20clients&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;). 이러한 실수를 피하려면, &lt;b&gt;함수 호출 전에 HasAuthority()나 IsLocallyControlled()로 현재 코드 실행 주체(서버/클라)를 확인&lt;/b&gt;하는 것이 좋습니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=servers%20have%20parallel%20execution.%20,scripts%2C%20as%20it%20is%20possible&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;) (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=RPC%20%EC%82%AC%EC%9A%A9%20%EB%B0%A9%EB%B2%95%3A%C2%A0%20%C2%A0Server%C2%A0%20vs,Client&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;함수 시그니처 및 네트워크 지원 여부:&lt;/b&gt; RPC 함수는 리턴값을 가질 수 없고, 특정한 파라미터 타입만 지원합니다. 지원되지 않는 큰 구조체나 객체 포인터 등을 인자로 사용하면 RPC 호출이 직렬화되지 않을 수 있습니다. 또한 함수 선언 시 UFUNCTION 매크로의 Net specifier(Server/Client/NetMulticast)와 _Implementation 구현을 제대로 했는지 점검해야 합니다. C++ 구현체 이름이 틀리거나(오타 등) 함수가 static으로 선언되면 RPC로 동작하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;네트워크 지연 및 드롭:&lt;/b&gt; Unreliable RPC의 경우 네트워크 상황에 따라 도착하지 않을 수도 있습니다. 만약 &lt;b&gt;간헐적으로 RPC가 실행되지 않는다면&lt;/b&gt;, 해당 RPC가 Unreliable인지 확인하고, 패킷 손실 상황을 의심해볼 수 있습니다. 이 경우 Reliable로 바꾸거나 보완 로직이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;디버깅 팁:&lt;/b&gt; RPC 문제를 추적할 때는 서버와 클라이언트 양쪽 로그를 활용하세요. _Implementation 함수 내에 UE_LOG로 메시지와 실행 주체(예: Role, GetWorld()-&amp;gt;IsServer() 등)를 출력해보면, 함수가 호출되었는지, 어느 측에서 실행됐는지 알 수 있습니다. 또한 &lt;b&gt;Breakpoints&lt;/b&gt;를 설정해 서버/클라별로 함수 진입을 확인하거나, GEngine-&amp;gt;AddOnScreenDebugMessage로 시각적으로 찍어볼 수도 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;네트워크 로그 및 프로파일러 활용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진은 네트워크 상태를 확인하고 RPC/복제 트래픽을 분석하기 위한 여러 도구를 제공합니다. 대표적으로 &lt;b&gt;콘솔 명령을 통한 상태 표시&lt;/b&gt;와 &lt;b&gt;Network Profiler&lt;/b&gt;가 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Stat Net:&lt;/b&gt; 게임 실행 중 콘솔에 stat net 입력하면 화면에 네트워크 통계가 표시됩니다. 현재 연결 수, 패킷 입출력량, 채널/배치 수 등 정보를 실시간으로 볼 수 있어, 트래픽이 급증하는 지점이나 ping/packet loss를 모니터링할 때 유용합니다 (&lt;a href=&quot;https://ikrima.dev/ue4guide/networking/debugging-tips-tricks/#:~:text=Debug%20Configuration%20,bytes%2C%20etc%3B%20stat%20game&quot;&gt;Debug Configuration - Gamedev Guide&lt;/a&gt;). stat net을 통해 RPC 호출 후 &lt;b&gt;출력 바이트 증감&lt;/b&gt;을 관찰하거나, &lt;b&gt;채널 수 증가&lt;/b&gt;(새 액터 replication 등)를 확인할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Net Debug 화면:&lt;/b&gt; 에디터 환경에서는 網 (백쿼트) 키나 특정 명령으로 네트워크 디버그 HUD를 띄울 수 있는 옵션이 있습니다. 이 HUD는 각 플레이어의 Role(Authority/Simulated) 등의 정보를 띄우고, 시각적으로 패킷 통신을 표현하기도 합니다. 프로젝트 설정에서 &lt;b&gt;Enable Network Debugging&lt;/b&gt;을 켜고 플레이하면, &lt;b&gt;' (작은 따옴표) 키&lt;/b&gt;로 HUD 토글이 가능했던 버전이 있습니다. (UE5에서는 방식이 변경될 수 있으니 공식 문서를 참고하세요.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Network Profiler 툴:&lt;/b&gt; 세밀한 네트워크 트래픽 분석에는 &lt;b&gt;네트워크 프로파일러&lt;/b&gt;를 사용합니다. 에디터 콘솔에 &lt;b&gt;netprofile&lt;/b&gt; 명령을 입력하면 프로파일링이 시작되고, 다시 입력하면 중지됩니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/network-how-to-debug-show-network-traffic-in-ue4/285213#:~:text=In%20order%20to%20view%20data,the%20statistics%20that%20were%20recorded&quot;&gt;Network - How to debug/show network traffic in UE4? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). 중지 후 &amp;lt;Project&amp;gt;/Saved/Profiling 폴더에 .nprof 확장자의 파일이 생성되며, &amp;lt;엔진설치&amp;gt;/Engine/Binaries/DotNET/NetworkProfiler.exe 프로그램으로 이 파일을 열 수 있습니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/network-how-to-debug-show-network-traffic-in-ue4/285213#:~:text=It%E2%80%99s%20located%20at%20,version%20%3E%2FEngine%2FBinaries%2FDotNET%2FNetworkProfiler.exe&quot;&gt;Network - How to debug/show network traffic in UE4? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;) (&lt;a href=&quot;https://forums.unrealengine.com/t/network-how-to-debug-show-network-traffic-in-ue4/285213#:~:text=In%20order%20to%20view%20data,the%20statistics%20that%20were%20recorded&quot;&gt;Network - How to debug/show network traffic in UE4? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). 프로파일러에서는 &lt;b&gt;프레임별로 어떤 Actor의 어떤 RPC/속성이 몇 바이트 전송됐는지&lt;/b&gt; 상세히 확인できます. 이를 통해 특정 RPC가 과도한 빈도로 호출되어 대역폭을 많이 차지하지 않는지, 복제된 변수 중 업데이트가 너무 빈번한 것은 없는지 최적화 단서를 얻을 수 있습니다. Tip: 서버 측에서 프로파일링하는 것이 더 많은 정보를 수집할 수 있으므로, 가능하면 서버 실행에 netprofile을 사용합니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/network-how-to-debug-show-network-traffic-in-ue4/285213#:~:text=recorded&quot;&gt;Network - How to debug/show network traffic in UE4? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기타 디버그 콘솔 명령:&lt;/b&gt; 네트워크 상황을 가정해볼 때는 net pktlag=X (지연), net pktloss=X(손실) 등의 명령으로 인위적인 랙/패킷 손실을 적용해 테스트할 수 있습니다. 예를 들어 net pktlag=100이면 왕복 100ms 지연을, net pktloss=0.1이면 10% 패킷 손실을 시뮬레이션합니다. 이러한 툴로 RPC의 신뢰성이나 보정 처리를 검증할 수 있습니다. 또한 log LogNetTraffic verbose 등의 로그 카테고리를 활성화하면 콘솔 로그로 어떤 RPC/재전송이 오가는지 세부 출력해주어 문제를 파악하는데 도움이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;RPC 성능 최적화 및 베스트 프랙티스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;효율적인 멀티플레이어를 위해서는 RPC 사용을 최적화하고, 불필요한 네트워크 부하를 줄이는 것이 중요합니다. 다음은 &lt;b&gt;실무에서 유용한 RPC 최적화 팁과 베스트 프랙티스&lt;/b&gt;입니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;RPC 최소화 및 RepNotify 활용:&lt;/b&gt; 불필요하게 자주 호출되는 RPC는 줄이는 것이 좋습니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=,will%20overflow%20the%20queue%20for&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 동일한 기능을 &lt;b&gt;Replicated 변수+OnRep로 대체&lt;/b&gt;할 수 있다면 그렇게 하세요. 예를 들어 플레이어 상태 변화처럼 &lt;b&gt;持続적인 상태&lt;/b&gt;는 RPC 여러 번보다 변수 하나 복제가 낫습니다. 가능하면 &lt;b&gt;하나의 RPC로 여러 정보를 묶어 보내거나&lt;/b&gt;, 아예 상태 복제로 처리하고 클라에서 OnRep로 처리하게 하는 편이 효율적입니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=,will%20overflow%20the%20queue%20for&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Multicast RPC 남용 금지:&lt;/b&gt; NetMulticast RPC는 &lt;b&gt;연결된 모든 클라이언트에 패킷을 보내므로&lt;/b&gt; 한 번 호출에 네트워크 부하가 클 수밖에 없습니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=,will%20overflow%20the%20queue%20for&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 특히 플레이어 수가 많을수록 부하가 기하급수적으로 늘어납니다. 따라서 &lt;b&gt;멀티캐스트는 꼭 필요한 경우에만 사용&lt;/b&gt;하고, 가능하면 빈도를 낮추세요 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=,will%20overflow%20the%20queue%20for&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 예를 들어, 총알 피격 이펙트를 매 발사마다 Multicast로 보내기보다는, 클라이언트들에 예측시키고 중요한 이벤트에만 Multicast를 사용하도록 설계합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 전용 로직 분리:&lt;/b&gt; 서버에서만 처리하면 되는 로직이라면 굳이 Server RPC로 만들 필요 없이, &lt;b&gt;Authority 체크 후 로컬 함수로 처리&lt;/b&gt;해도 됩니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=,often%20players%20can%20activate%20these&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). Server RPC는 클라이언트가 호출할 필요가 있을 때만 쓰고, 그 외 서버 내부 로직은 일반 함수로 두는 편이 간결하고 성능에도 이롭습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Reliable RPC 입력 스팸 대처:&lt;/b&gt; 플레이어 입력에 직접 Reliable RPC를 바인딩하면 버튼 연타 시 &lt;b&gt;네트워크 큐가 넘칠 수 있습니다&lt;/b&gt; (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=server.%20,them%20in%20response%20to%20gameplay&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 예를 들어 사격 버튼을 빠르게 누르면 매번 RPC가 쌓여 지연이나 overflow가 발생할 수 있으므로, &lt;b&gt;발사 속도 제한&lt;/b&gt;이나 &lt;b&gt;쿨다운 타이머&lt;/b&gt;를 두어 호출 빈도를 제어합니다. 또는 잦은 입력은 애초에 Unreliable로 보내고 서버에서 시간차를 감안해 처리하는 방법도 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;High-frequency RPC는 Unreliable:&lt;/b&gt; 앞서 언급했듯, &lt;b&gt;틱당 호출되거나 초당 수십 회 이상의 RPC는 Unreliable로 보내는 것이 원칙&lt;/b&gt;입니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=activate%20these.%20,in%20functions%20that%20activate%20on&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). Reliable는 ACK 대기 때문에 여러 개가 밀리면 응답 지연이 누적될 수 있습니다. 또한 Reliable 채널은 한 Actor당 한 큐로 관리되므로 너무 많은 Reliable RPC를 보내면 &lt;b&gt;채널 버퍼 오버플로&lt;/b&gt;로 연결이 끊길 수 있습니다 (&lt;a href=&quot;https://www.reddit.com/r/Battalion1944/comments/844s5a/our_lead_programmer_james_tatum_explaining/#:~:text=,the%20connection%20will%20be%20terminated&quot;&gt;Our lead programmer James Tatum explaining 'Outgoing Reliable Buffer Overflow' from March 8th update! : r/Battalion1944&lt;/a&gt;) (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=server.%20,them%20in%20response%20to%20gameplay&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 그런 사례를 방지하려면 일부 호출은 과감히 손실을 허용(Unreliable)하고, 정말 중요한 것만 Reliable로 구분하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;중복 RPC 호출 피하기:&lt;/b&gt; 동일한 효과나 결과를 초당 여러 번 RPC로 보내는 실수를 피합니다. 예를 들어 폭발 지속 데미지를 매 프레임 RPC로 보내기보다, 서버에서 누적 계산 후 한 번만 결과를 보내는 식으로 &lt;b&gt;빈도 감소&lt;/b&gt;를 고려해야 합니다. Network Profiler로 어떤 RPC가 많이 호출되는지 분석해보고, &lt;b&gt;합칠 수 있는 것은 하나로 합치고, 불필요한 것은 제거&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RPC + RepNotify 병행 패턴:&lt;/b&gt; 게임 로직에 따라 서버와 클라이언트 모두에서 동일하게 처리해야 하는 경우, &lt;b&gt;RPC로 서버&amp;rarr;클라 함수를 호출하면서 서버에서도 같은 로직을 실행&lt;/b&gt;하거나, &lt;b&gt;RepNotify로 한쪽에서 변경을 트리거&lt;/b&gt;하는 패턴을 사용합니다 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=,and%20servers%20have%20parallel%20execution&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 예를 들어 탄약 감소 시 서버에서 RPC로 클라이언트 UI를 업데이트시킬 뿐 아니라, 서버 자신도 HUD를 업데이트해야 한다면 RPC 호출 후 서버측에서도 함수를 한 번 더 호출하거나, 탄약 변수 RepNotify로 처리하여 서버/클라 모두 적용합니다. 이런 기법으로 &lt;b&gt;서버와 클라이언트의 상태를 동기화&lt;/b&gt;시키면서 불필요한 이중 호출을 줄일 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로, 네트워크 최적화는 지속적인 프로파일링과 튜닝이 필요합니다. &lt;b&gt;Network Profiler&lt;/b&gt;를 주기적으로 활용하여 &lt;b&gt;대역폭 사용량 상위 항목&lt;/b&gt;을 점검하고, RPC 호출 횟수와 크기를 모니터링하세요. 또한 플레이테스트에서 &lt;b&gt;패킷 손실이나 지연 상황을 시뮬레이션&lt;/b&gt;해 RPC가 제대로 동작하는지 (특히 Reliable overflow가 없는지) 확인하는 것이 좋습니다. 이러한 트러블슈팅과 최적화 과정을 거치면, 보다 견고하고 효율적인 멀티플레이어 시스템을 구축할 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고 자료:&lt;/b&gt; 언리얼 엔진 공식 문서와 커뮤니티 글에서는 RPC와 Replication에 대한 심층적인 설명과 모범 사례를 제공하고 있습니다 (&lt;a href=&quot;https://cedric-neukirchen.net/docs/multiplayer-compendium/remote-procedure-calls#:~:text=1,Multicast%20RPCs%20are%20an%20exception&quot;&gt;Remote Procedure Calls | An Unreal Engine Blog by Cedric Neukirchen&lt;/a&gt;) (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=server.%20,them%20in%20response%20to%20gameplay&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;). 이러한 리소스를 적극 참고하여 네트워크 기능을 설계하면 많은 도움이 됩니다. 특히 Epic 공식 문서의 &quot;Testing, Profiling, and Debugging Networking&quot; 글과 멀티플레이어 컴펜디엄 자료 (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=,will%20overflow%20the%20queue%20for&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;) (&lt;a href=&quot;https://designerd.tistory.com/entry/UE-Net-RPC-Remote-Procedure-Call#:~:text=,RPC%EB%B3%B4%EB%8B%A4%20Property%20Replication%EC%9D%84%20%EC%82%AC%EC%9A%A9%ED%95%A0%20%EA%B2%83&quot;&gt;[UE Net] RPC (Remote Procedure Call)&lt;/a&gt;)를 추천드립니다.&lt;/p&gt;</description>
      <category>UnrealCamp/네트워크 스터디[일부공개]</category>
      <author>남찬우</author>
      <guid isPermaLink="true">https://kakaoncw.tistory.com/89</guid>
      <comments>https://kakaoncw.tistory.com/89#entry89comment</comments>
      <pubDate>Fri, 14 Mar 2025 18:57:30 +0900</pubDate>
    </item>
    <item>
      <title>저장 시스템</title>
      <link>https://kakaoncw.tistory.com/88</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저장 버튼 또는 특정 이벤트(예: 게임 종료, 스테이지 클리어 등) 시 데이터를 저장하는 방식&lt;/li&gt;
&lt;li&gt;저장할 데이터 구조 설계(예: 플레이어 상태, 인벤토리, 진행도, 맵 상태 등)&lt;/li&gt;
&lt;li&gt;권장되는 저장 형식(예: JSON, 바이너리, SQL 등)과 각각의 장단점&lt;/li&gt;
&lt;li&gt;언리얼 엔진에서 저장 및 로드를 구현하는 구체적인 방법(USaveGame 클래스 활용, 파일 시스템 접근, Steam 클라우드 지원 여부 등)&lt;/li&gt;
&lt;li&gt;리슨 서버 환경에서 클라이언트-호스트 간 데이터 동기화 고려 사항&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 내용을 바탕으로 정리해서 알려드리겠습니다. 곧 결과를 공유해드릴게요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;멀티플레이 로그라이크 게임의 세이브/로드 시스템 구현 가이드&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;저장 이벤트와 저장 시점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티플레이 로그라이크 게임에서는 실시간 자동 저장보다는 특정 이벤트에서만 저장을 수행하는 것이 일반적입니다. 예를 들면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플레이어가 메뉴에서 &lt;b&gt;저장&lt;/b&gt; 버튼을 눌렀을 때 (사용자 요청 시).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스테이지 클리어&lt;/b&gt; 후 다음 단계로 넘어가기 전에.&lt;/li&gt;
&lt;li&gt;게임을 &lt;b&gt;종료&lt;/b&gt;할 때 진행 상황을 저장 (진행 중 임의로 저장하지 않고 종료 시 한 번만 저장).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 이벤트 기반으로 저장함으로써 로그라이크 특유의 긴장감을 유지하고, 불필요한 빈번한 저장으로 인한 성능 저하를 피할 수 있습니다. 특히 리슨 서버 방식의 멀티플레이에서는 호스트가 임의로 잦은 자동 저장을 발생시키면 모든 참가자의 게임이 순간 멈추는 등 불편을 줄 수 있습니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/listen-server-saving/352814#:~:text=If%20a%20game%20is%20silently,game%2C%20they%20might%20get%20annoyed&quot;&gt;Listen Server, saving? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). 일반적으로 저장 동작은 순간적인 프레임 드롭을 유발할 수 있으므로, 저장 시에는 게임을 일시 정지하거나 짧은 로딩 화면을 표시하는 것이 좋습니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/listen-server-saving/352814#:~:text=If%20a%20game%20is%20silently,game%2C%20they%20might%20get%20annoyed&quot;&gt;Listen Server, saving? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). 따라서 저장 지점은 자주 발생하지 않도록 설계하고, 플레이어에게도 그 시점을 명확히 인지시키는 것이 바람직합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;저장할 데이터 설계 및 확장성 고려&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 데이터를 저장할지 설계할 때, 게임의 진행 상황을 복원하는 데 필요한 모든 정보를 포함해야 합니다. 대표적으로:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;플레이어 상태&lt;/b&gt;: 각 플레이어의 현재 능력치(체력, 경험치, 레벨 등), 위치, 보유 화폐(게임 내 통화) 등을 저장합니다. 멀티플레이의 경우 플레이어별로 이 정보를 구분해서 저장해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인벤토리와 장비&lt;/b&gt;: 플레이어가 소지한 아이템 목록, 장비 내구도, 착용 중인 아이템 등을 저장합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;진행도 및 게임 상태&lt;/b&gt;: 현재 진행 중인 스테이지/층 번호, 난이도, 점수, 랜덤 시드(seed) 값 등 게임 진행을 나타내는 정보를 기록합니다. 로그라이크에서는 맵 생성에 랜덤 요소가 있으므로 시드값이나 맵 인덱스를 저장해 두면 로드 시 동일한 맵을 복원할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;월드(맵) 상태&lt;/b&gt;: 맵에 존재하는 중요 오브젝트들의 상태를 저장합니다. 예를 들어 이미 획득한 아이템이나 열린 보물상자, 처치된 보스처럼 &lt;b&gt;게임 세계의 변화&lt;/b&gt;에 해당하는 요소들을 기록해야 합니다. 맵 지형 자체가 고정적이라면 따로 저장할 필요가 없지만, 플레이어의 상호작용으로 변경되는 오브젝트(열린 상자, 사라진 아이템 등)는 저장해두어야 합니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/listen-server-saving/352814#:~:text=If%20the%20client%20has%20access,whether%20those%20objects%20are%20still&quot;&gt;Listen Server, saving? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동적 확장성&lt;/b&gt;을 고려하여 데이터 구조를 설계하는 것도 중요합니다. 게임 업데이트로 새로운 요소(예: 신규 아이템 종류나 플레이어 능력치)가 추가되더라도 기존 세이브 파일을 불러올 수 있어야 합니다. 이를 위해 보통 &lt;b&gt;버전 번호&lt;/b&gt;를 세이브 데이터에 포함시켜 버전에 따라 로드 방식을 다르게 처리하거나 변환 과정을 거칩니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1c6bxr0/save_games_as_json_or_any_text_instead_of_binary/#:~:text=%E2%80%A2&quot;&gt;Save games as JSON (or any text) instead of binary? : r/unrealengine&lt;/a&gt;). 또한 Unreal Engine의 저장 시스템(USaveGame)은 UPROPERTY에 SaveGame 플래그가 지정된 변수들을 &lt;b&gt;이름과 함께&lt;/b&gt; 직렬화하기 때문에, 변수를 새로 추가하는 정도의 변경은 이전 버전 세이브와도 호환되는 편입니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1c6bxr0/save_games_as_json_or_any_text_instead_of_binary/#:~:text=For%20me%20FMemoryWriter%20%2F%20FMemoryReader,binary%20archive&quot;&gt;Save games as JSON (or any text) instead of binary? : r/unrealengine&lt;/a&gt;). 반대로 변수를 삭제하거나 자료구조를 크게 변경하면 호환성이 깨질 수 있으므로, 그런 경우 세이브 파일의 버전을 확인하여 데이터 변환을 수행하거나 필요시 호환 경고 및 새로운 세이브 파일 생성 등의 대비책을 마련해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티플레이 게임에서는 &lt;b&gt;플레이어별 데이터 분리&lt;/b&gt;도 고려해야 합니다. 예를 들어, 호스트는 게임의 &lt;b&gt;월드 상태와 진행도&lt;/b&gt;를 저장하고 각 클라이언트는 자신의 &lt;b&gt;캐릭터 상태&lt;/b&gt;를 별도로 저장하는 식으로 역할을 나눌 수 있습니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/listen-server-saving/352814#:~:text=Ok%20I%20think%20I%20get,like%20how%20minecraft%20does%20it&quot;&gt;Listen Server, saving? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). 이렇게 하면 이후 재접속 시 호스트는 월드 진행도를 복원하고, 각 플레이어는 자신의 진행도(캐릭터 능력치, 아이템 등)를 개별적으로 복원할 수 있습니다. 실제로 마인크래프트 등의 게임도 이와 유사하게, 호스트는 세계를 저장하고 각 플레이어는 자기 인벤토리를 자기 PC에 저장하는 방식으로 동작합니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/listen-server-saving/352814#:~:text=Ok%20I%20think%20I%20get,like%20how%20minecraft%20does%20it&quot;&gt;Listen Server, saving? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;).&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;저장 데이터 형식 선택과 장단점 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 세이브 데이터를 저장할 때는 여러 &lt;b&gt;파일 형식&lt;/b&gt;을 선택할 수 있으며, 각각 장단점이 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;JSON (텍스트 기반)&lt;/b&gt;:&lt;br /&gt;사람이 읽을 수 있는 텍스트 포맷으로, 데이터 구조를 계층적으로 표현하기에 적합합니다 (&lt;a href=&quot;https://gamedev.stackexchange.com/questions/19046/what-is-a-good-file-format-for-saving-game-data#:~:text=To%20display%20hierachical%20data%2C%20YAML,easier%20to%20parse%20than%20XML&quot;&gt;java - What is a good file format for saving game data? - Game Development Stack Exchange&lt;/a&gt;). 개발 중 디버깅에 유리하고, 키-값 구조여서 필드가 추가되거나 순서가 바뀌어도 비교적 유연하게 대응할 수 있습니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1c6bxr0/save_games_as_json_or_any_text_instead_of_binary/#:~:text=This%20would%20be%20beneficial%20for,it%20more%20resilient%20during%20development&quot;&gt;Save games as JSON (or any text) instead of binary? : r/unrealengine&lt;/a&gt;).&lt;br /&gt;단점으로는 파일 용량이 이진(binary) 형식보다 크고 읽기/쓰기 속도가 느린 편입니다. 또한 사용자가 파일을 열어 내용을 수정하기 쉽다는 점에서 치트에 노출될 수 있습니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1c6bxr0/save_games_as_json_or_any_text_instead_of_binary/#:~:text=SeligFay&quot;&gt;Save games as JSON (or any text) instead of binary? : r/unrealengine&lt;/a&gt;). JSON 파일이 잘못 편집되면 파싱 오류로 세이브 데이터를 읽지 못하게 될 수 있으므로, 필요한 경우 &lt;b&gt;암호화&lt;/b&gt;나 무결성 검증 등의 대책을 추가로 고려해야 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;바이너리 (이진 데이터)&lt;/b&gt;:&lt;br /&gt;Unreal Engine의 기본 SaveGame 시스템이 사용하는 형식으로, 사람이 읽을 수 없는 &lt;b&gt;이진 데이터&lt;/b&gt;로 저장합니다. 구조체나 객체 메모리를 직렬화하여 기록하므로 파일 크기가 작고, 텍스트보다 저장/로드 속도가 빠른 것이 장점입니다. 특히 자동 저장이나 빈번한 저장이 필요한 경우 이진 포맷이 퍼포먼스 측면에서 유리합니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1c6bxr0/save_games_as_json_or_any_text_instead_of_binary/#:~:text=You%20could%20do%20both,you%20have%20an%20autosave%20feature&quot;&gt;Save games as JSON (or any text) instead of binary? : r/unrealengine&lt;/a&gt;).&lt;br /&gt;이진 데이터는 사용자가 내용을 임의로 편집하기 어려워 &lt;b&gt;세이브 파일 변조&lt;/b&gt;(치트)를 어느 정도 억제하는 효과도 있습니다. 하지만 파일을 사람이 직접 해석하기 힘들고, 포맷이 변경되면 호환성 문제가 발생할 수 있습니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1c6bxr0/save_games_as_json_or_any_text_instead_of_binary/#:~:text=This%20would%20be%20beneficial%20for,it%20more%20resilient%20during%20development&quot;&gt;Save games as JSON (or any text) instead of binary? : r/unrealengine&lt;/a&gt;). 다행히 Unreal의 기본 세이브 시스템은 변경된 각 변수마다 이름과 타입 정보를 함께 저장하여 &lt;b&gt;포맷 변경에 비교적 안정적&lt;/b&gt;이도록 설계되어 있습니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/1c6bxr0/save_games_as_json_or_any_text_instead_of_binary/#:~:text=For%20me%20FMemoryWriter%20%2F%20FMemoryReader,binary%20archive&quot;&gt;Save games as JSON (or any text) instead of binary? : r/unrealengine&lt;/a&gt;). 그렇더라도 내부 구조 변경 시에는 앞서 언급한 버전 관리 전략을 함께 사용하는 것이 안전합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터베이스 (SQL 기반)&lt;/b&gt;:&lt;br /&gt;SQLite 같은 &lt;b&gt;로컬 데이터베이스&lt;/b&gt; 파일에 저장하거나, &lt;b&gt;원격 DB 서버&lt;/b&gt;(예: MySQL)에 저장하는 방식입니다. 데이터베이스를 사용하면 테이블로 구조화된 방대한 데이터를 효율적으로 관리할 수 있고, SQL 쿼리를 통해 특정 데이터만 빠르게 조회하거나 통계를 낼 수 있다는 장점이 있습니다. 특히 MySQL처럼 서버 DB를 사용하면 Steam 같은 플랫폼에 의존하지 않고도 중앙 서버에 데이터를 보관하여 치트를 방지하거나, 다양한 사용자 통계 데이터를 수집할 수 있습니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/save-thinks-into-steam-cloud/102615#:~:text=not%20prevent%20that%20completely%20,hacking&quot;&gt;Save thinks into Steam Cloud? - Blueprint - Epic Developer Community Forums&lt;/a&gt;).&lt;br /&gt;그러나 데이터베이스를 활용하려면 추가적인 &lt;b&gt;인프라와 구현&lt;/b&gt;이 필요합니다. 게임 클라이언트가 네트워크를 통해 서버와 통신하는 로직을 작성해야 하고, 직접 DB 서버를 운영해야 한다면 비용과 관리 부담이 큽니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/how-is-currency-stored-for-the-player/917436#:~:text=If%20you%20want%20to%20store,game%20%2F%20a%20personal%20project&quot;&gt;How is &quot;Currency&quot; Stored for the Player - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). SQLite와 같은 파일 기반 DB는 별도 서버 없이 동작하지만, 결국 로컬 파일이므로 치트 방지 면에서는 일반 세이브 파일과 큰 차이가 없습니다. 또한 SQL 데이터를 사용하려면 직렬화/역직렬화보다는 SQL문 작성 및 파싱 등 복잡도가 높아집니다. 일반적으로 인디 게임이나 소규모 프로젝트에서는 이런 방식보다는 Steam 클라우드 또는 로컬 세이브 파일 방식(필요시 암호화)을 더 많이 활용합니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/how-is-currency-stored-for-the-player/917436#:~:text=You%20can%20either%20store%20this,Unreal%20Engine%20Documentation&quot;&gt;How is &quot;Currency&quot; Stored for the Player - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;언리얼 엔진에서의 세이브 및 로드 구현 방법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;USaveGame 클래스를 활용한 저장&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unreal Engine에는 세이브용으로 설계된 USaveGame 클래스를 활용하는 것이 권장됩니다. 먼저 USaveGame을 상속한 커스텀 세이브게임 클래스를 만들고, 그 안에 저장하고자 하는 변수들을 정의합니다 (&lt;a href=&quot;https://www.tomlooman.com/unreal-engine-cpp-save-system/#:~:text=Unreal%20has%20a%20built,To%20decide%20which%20variables%20to&quot;&gt;Unreal Engine C++ Save System (SaveGame) - Tom Looman&lt;/a&gt;). (블루프린트에서는 &lt;b&gt;Add New -&amp;gt; Blueprint Class&lt;/b&gt; 생성 시 부모 클래스로 SaveGame을 선택하여 만들 수 있습니다.) 게임 내에서 저장 이벤트가 발생하면, 현재 게임 상태 값을 이 SaveGame 오브젝트의 변수들에 복사합니다. 예를 들어 플레이어의 HP나 경험치, 인벤토리 목록 등의 값을 SaveGame 객체의 대응 변수에 설정합니다. 여러 플레이어의 데이터를 저장해야 한다면 SaveGame 객체 내에 플레이어별 구조체 배열 등을 마련하여 복수의 캐릭터 정보를 넣을 수 있습니다. 월드상의 오브젝트 상태를 저장할 때는 해당 오브젝트가 열렸는지/획득되었는지 여부 등을 SaveGame에 기록합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 준비된 SaveGame 오브젝트를 UGameplayStatics::SaveGameToSlot 함수 (블루프린트 노드 &lt;b&gt;Save Game to Slot&lt;/b&gt;)로 디스크에 저장합니다. 이 함수 호출 시 지정한 슬롯 이름과 사용자 인덱스(일반적으로 0)에 대응하는 파일이 생성되며, 기본적으로 게임 프로젝트의 Saved/SaveGames 폴더 아래에 .sav 확장자의 세이브 파일이 저장됩니다 (&lt;a href=&quot;https://www.tomlooman.com/unreal-engine-cpp-save-system/#:~:text=then%20pass%20all%20these%20variables,matched&quot;&gt;Unreal Engine C++ Save System (SaveGame) - Tom Looman&lt;/a&gt;). 슬롯 이름을 다르게 주거나 사용자 인덱스를 활용하면 여러 개의 세이브 파일을 관리할 수도 있습니다 (예: 슬롯1, 슬롯2 여러 개의 저장 기록).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드할 때는 UGameplayStatics::LoadGameFromSlot (블루프린트 노드 &lt;b&gt;Load Game from Slot&lt;/b&gt;)을 사용하여 파일에서 SaveGame 객체를 불러옵니다. 그런 다음 그 안에 저장된 값을 게임의 현재 객체들에 적용해줍니다. 플레이어의 위치, 능력치, 인벤토리 등는 SaveGame에서 읽은 값으로 설정하고, 필요하면 월드의 오브젝트를 생성하거나 상태를 갱신합니다. 예를 들어 저장된 데이터에 해당 아이템이 이미 획득된 것으로 표시돼 있다면, 로드 시 그 아이템을 맵에 나타나지 않게 하거나 이미 없어진 상태로 설정합니다. 이 과정은 게임 시작 시 또는 플레이어가 이어하기를 선택했을 때 수행합니다. &lt;b&gt;멀티플레이의 경우 호스트가 세이브 데이터를 불러와 서버의 게임 상태를 복원한 뒤, 해당 상태를 클라이언트들에게 동기화&lt;/b&gt;해야 합니다. 서버에서 플레이어 상태나 월드 정보를 로드하여 설정하면, 그 값들이 Unreal의 &lt;b&gt;Replication&lt;/b&gt; 시스템을 통해 자동으로 클라이언트들에 전파되므로 결과적으로 모두 동일한 상태로 갱신됩니다 (&lt;a href=&quot;https://www.tomlooman.com/unreal-engine-cpp-save-system/#:~:text=Loading%20the%20game%20will%20basically,matched&quot;&gt;Unreal Engine C++ Save System (SaveGame) - Tom Looman&lt;/a&gt;). (예를 들어 서버에서 플레이어의 체력을 로드하여 설정하면 그 값이 복제되어 각 클라이언트의 플레이어 화면에도 동일한 체력이 반영됩니다.)&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고:&lt;/b&gt; SaveGame 시스템을 사용하지 않고 직접 파일을 다루는 것도 가능합니다. C++의 FFileHelper::SaveStringToFile 등을 이용하면 원하는 경로에 텍스트 데이터를 쓸 수 있고, FArchive를 사용하면 바이너리 데이터도 기록할 수 있습니다. 예를 들어 JSON 형식으로 저장하려면 게임 데이터를 FJsonObject로 구성하고 FFileHelper::SaveStringToFile로 JSON 문자열을 파일에 저장하면 됩니다. 다만 이러한 방법은 파일 경로 관리, 직렬화/파싱 로직을 수동 구현해야 하므로 SaveGame보다 복잡도가 높습니다. 특별한 이유가 있다면 몰라도, 일반적인 게임 세이브에는 SaveGame 클래스를 활용하는 것이 개발 속도와 안정성 면에서 유리합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Steam 클라우드 저장 연동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PC 플랫폼(스팀)에서는 &lt;b&gt;Steam Cloud&lt;/b&gt;를 통해 플레이어의 세이브 데이터를 클라우드에 백업/동기화할 수 있습니다. Unreal Engine 자체가 이를 자동 처리해주지는 않지만, Steam의 &lt;b&gt;Auto-Cloud&lt;/b&gt; 기능을 설정하면 구현이 간편합니다. Steamworks 설정에서 지정한 경로(예: 세이브 파일 경로)에 있는 파일들을 Steam 클라우드와 동기화하도록 설정할 수 있으며 (&lt;a href=&quot;https://forums.unrealengine.com/t/save-thinks-into-steam-cloud/102615#:~:text=In%20the%20end%20you%20should,into%20the%20steam%20coud%20api&quot;&gt;Save thinks into Steam Cloud? - Blueprint - Epic Developer Community Forums&lt;/a&gt;), 이렇게 해두면 게임이 종료될 때 해당 세이브 파일을 Steam 클라이언트가 자동으로 업로드해주고 게임 시작 시 다운로드해줍니다. Unreal의 SaveGame으로 생성된 세이브 파일은 기본적으로 로컬에 저장되므로, 개발자는 특별한 작업 없이 &lt;b&gt;로컬 세이브/로드를 그대로 하면 되고 Steam이 백그라운드에서 알아서 클라우드 동기화&lt;/b&gt;를 해줍니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/save-thinks-into-steam-cloud/102615#:~:text=In%20the%20end%20you%20should,into%20the%20steam%20coud%20api&quot;&gt;Save thinks into Steam Cloud? - Blueprint - Epic Developer Community Forums&lt;/a&gt;). (반드시 Steamworks 대시보드에서 Auto-Cloud 대상 경로 설정을 해두어야 하며, 이때 기본 세이브 경로인 AppData/Local/&amp;lt;GameName&amp;gt;/Saved/SaveGames/ 등을 등록해야 합니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Steam 클라우드를 프로그래밍적으로 제어하려면 Steamworks SDK의 &lt;b&gt;Remote Storage API&lt;/b&gt;를 직접 사용해야 합니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/save-thinks-into-steam-cloud/102615#:~:text=You%20can%20write%20information%20into,here%20is%20an%20API%20reference&quot;&gt;Save thinks into Steam Cloud? - Blueprint - Epic Developer Community Forums&lt;/a&gt;). 예를 들어 Valve가 제공하는 ISteamRemoteStorage 인터페이스를 C++로 호출하여 파일을 업로드/다운로드하거나, Steam Cloud에 여러 개의 파일 목록을 관리할 수 있습니다. 하지만 이 API는 언리얼의 블루프린트에서 바로 접근할 수는 없고, 개발자가 C++ 코드로 래핑하여 호출한 뒤 블루프린트로 연동해야 합니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/7hrwjj/steam_cloud_saving_system/#:~:text=So%20long%20as%20the%20user,will%20always%20be%20a%20possibility&quot;&gt;Steam Cloud saving system : r/unrealengine&lt;/a&gt;). 대부분의 경우 Auto-Cloud로 충분하며, &lt;b&gt;Steam 클라우드는 사용자의 로컬에 저장된 세이브를 기준으로 동작&lt;/b&gt;합니다. 게임 중 네트워크가 끊겨 있더라도 세이브는 일단 로컬에 되고, 나중에 Steam이 연결 가능해지면 해당 파일을 업로드합니다 (&lt;a href=&quot;https://www.reddit.com/r/unrealengine/comments/7hrwjj/steam_cloud_saving_system/#:~:text=You%20can%27t%20force%20Steam%20Cloud,can%2C%20but%20that%27s%20about%20it&quot;&gt;Steam Cloud saving system : r/unrealengine&lt;/a&gt;). 따라서 오프라인 상태에서도 세이브/로드는 로컬 진행되고, 추후 자동으로 클라우드에 반영됩니다. (Steam Cloud를 사용하지 않는 플랫폼이거나 사용자라면 당연히 세이브는 로컬에만 남으므로, 항상 로컬 세이브 파일을 안전하게 관리하는 로직이 기본이 되어야 합니다.)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리슨 서버 환경에서의 데이터 동기화 고려 사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서버-클라이언트 저장 역할 분담:&lt;/b&gt; 리슨 서버(호스트) 기반 게임에서는 &lt;b&gt;호스트가 주요 게임 상태를 저장&lt;/b&gt;하고, 각 &lt;b&gt;클라이언트는 자기 캐릭터 정보만 저장&lt;/b&gt;하는 방식으로 분담하는 것이 효율적입니다 (&lt;a href=&quot;https://forums.unrealengine.com/t/listen-server-saving/352814#:~:text=Ok%20I%20think%20I%20get,like%20how%20minecraft%20does%20it&quot;&gt;Listen Server, saving? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;). 호스트는 월드 진행 상황과 적 상태 등 &lt;b&gt;전체 게임 세계의 상태&lt;/b&gt;를 책임지고, 클라이언트는 자신의 &lt;b&gt;개인 진행 상황&lt;/b&gt;(예: 캐릭터 능력치, 인벤토리)을 자신의 PC에 저장하는 식입니다. 클라이언트는 자신의 캐릭터 정보는 모두 알고 있지만, 다른 플레이어나 월드 전체의 상태 정보까지 완전히 가지고 있지는 않을 수 있으므로 (&lt;a href=&quot;https://forums.unrealengine.com/t/listen-server-saving/352814#:~:text=,good%20to%20go%21%20Image%3A%20%3Aslight_smile&quot;&gt;Listen Server, saving? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;) 월드/세션 진행도 저장은 호스트가 수행해야 정확합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;데이터 저장 동기화:&lt;/b&gt; 저장 직전에 호스트와 클라이언트 간 게임 상태가 일치하도록 동기화하는 것이 중요합니다. 예를 들어 클라이언트가 어떤 아이템을 주웠다면 즉시 서버에 반영되어 호스트가 그 정보를 갖고 있어야 이후 세이브에 빠짐없이 기록됩니다. 일반적으로 &lt;b&gt;저장 트리거는 호스트에서 발생&lt;/b&gt;시키고, 필요하면 호스트가 각 클라이언트에 RPC를 보내 **자신의 데이터(프로필)**를 로컬에 저장하도록 유도할 수 있습니다. 로드 시에는 호스트가 저장했던 월드 데이터를 복원하고, 플레이어들의 위치나 상태를 세이브 시점으로 되돌린 후 클라이언트들을 접속시킵니다. 이때 각 플레이어의 캐릭터 상태는 &lt;b&gt;호스트가 저장해둔 스냅샷 데이터를 적용&lt;/b&gt;할 수도 있고, 각 클라이언트가 &lt;b&gt;자신의 로컬 세이브 데이터를 불러와&lt;/b&gt; 서버에 동기화할 수도 있습니다. 어느 방식을 택하든 최종적으로 &lt;b&gt;서버의 플레이어 상태를 갱신&lt;/b&gt;하고 나면 그 값이 Replication을 통해 클라이언트들과 동기화되어 모두 동일한 게임 상태를 갖게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;세이브 파일 접근 범위:&lt;/b&gt; 세이브 파일은 각 플레이어의 로컬 머신에 저장되므로, &lt;b&gt;호스트의 월드 세이브 파일은 해당 호스트가 다시 게임을 열 때&lt;/b&gt;만 직접 활용될 수 있습니다. 다른 클라이언트는 호스트의 세이브 파일에 접근할 수 없기 때문에, 만약 세션을 이어서 플레이하려면 원래 호스트가 다시 호스트를 맡아야 합니다 (호스트가 저장한 세계를 불러오려면 동일한 PC에서 호스팅해야 함). 따라서 멀티플레이 세션 진행도는 호스트에게 귀속된다고 볼 수 있습니다. 대신 각 &lt;b&gt;클라이언트의 캐릭터 진척도는 해당 클라이언트의 로컬에 저장&lt;/b&gt;되어 있으므로, 예를 들어 다른 사용자의 세션에 참여하더라도 자신의 &lt;b&gt;프로필 진행&lt;/b&gt;(영구 언락, 캐릭터 레벨 등)은 유지되도록 설계하는 것이 일반적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;권한(Authority) 처리:&lt;/b&gt; 구현 시에는 서버/클라이언트 권한을 명확히 구분해야 데이터 불일치가 없습니다. 세이브/로드 동작은 &lt;b&gt;서버에서 실행&lt;/b&gt;되도록 하고, 클라이언트가 이를 요청하면 서버에 호출을 위임하는 방식이 안전합니다. 예를 들어 클라이언트의 저장 버튼 입력 -&amp;gt; 서버의 GameMode에서 세이브 수행 순서로 처리합니다. 또한 객체의 상태를 로드하여 적용할 때 &lt;b&gt;서버에서 변수 값을 변경&lt;/b&gt;하면 자동으로 클라이언트들에게 복제되지만, 클라이언트가 자신의 화면에서만 값을 바꿔봐야 권한이 없으면 서버에 반영되지 않습니다. 따라서 세이브 파일로부터 게임 상태를 복원하는 코드는 주로 &lt;b&gt;GameMode 또는 서버 권한이 있는 컴포넌트&lt;/b&gt;에서 실행하고, 클라이언트 측에서는 필요 시 자신의 SaveGame을 로드하여 서버에 전달하는 정도로 역할을 제한합니다. 이렇게 하면 데이터 동기화와 권한 문제가 최소화되어 신뢰성을 높일 수 있습니다. (&lt;a href=&quot;https://forums.unrealengine.com/t/listen-server-saving/352814#:~:text=Regardless%20of%20whether%20the%20server,make%20changes%20in%20the%20future&quot;&gt;Listen Server, saving? - Multiplayer &amp;amp; Networking - Epic Developer Community Forums&lt;/a&gt;)&lt;/p&gt;</description>
      <category>UnraealEngine</category>
      <author>남찬우</author>
      <guid isPermaLink="true">https://kakaoncw.tistory.com/88</guid>
      <comments>https://kakaoncw.tistory.com/88#entry88comment</comments>
      <pubDate>Fri, 14 Mar 2025 18:57:18 +0900</pubDate>
    </item>
  </channel>
</rss>