GAS 네트워크 복제
멀티플레이어 게임을 위한 GAS 동기화 전략
복제 모드 이해
GameplayEffect 리플리케이션 전략
| 모드 | GE 복제 | Cues 복제 | 대역폭 | 권장 대상 |
|---|---|---|---|---|
| Full | 모든 클라이언트 | 복제됨 | 높음 | 싱글플레이어, 소규모 |
| Mixed | 소유자만 | 모든 클라이언트 | 중간 | 플레이어 캐릭터 |
| Minimal | 복제 안 함 | 복제됨 | 낮음 | AI, NPC |
// 리플리케이션 모드 설정
AMyCharacter::AMyCharacter()
{
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(
TEXT("AbilitySystemComponent"));
// 복제 활성화
AbilitySystemComponent->SetIsReplicated(true);
// 플레이어: Mixed 모드 (권장)
AbilitySystemComponent->SetReplicationMode(
EGameplayEffectReplicationMode::Mixed);
}
AMyAICharacter::AMyAICharacter()
{
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(
TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
// AI: Minimal 모드 (대역폭 절약)
AbilitySystemComponent->SetReplicationMode(
EGameplayEffectReplicationMode::Minimal);
}
Prediction과 Rollback
클라이언트 예측과 서버 확인
GAS는 클라이언트 예측(Prediction)을 지원하여 입력 지연을 최소화합니다. 서버가 결과를 확인하면 필요시 롤백됩니다.
// LocalPredicted 어빌리티 실행 흐름
1. [클라이언트] 플레이어 입력
|
v
2. [클라이언트] 어빌리티 즉시 실행 (예측)
- 로컬에서 GameplayEffect 적용
- 애니메이션 시작
- 쿨다운 시작 (예측)
|
v
3. [클라이언트 -> 서버] RPC로 활성화 요청 전송
|
v
4. [서버] 어빌리티 활성화 검증
- 조건 확인
- 실제 GameplayEffect 적용
|
v
5. [서버 -> 클라이언트] 결과 확인/거부
|
v
6. [클라이언트] 서버 결과에 따라:
- 성공: 예측 유지
- 실패: 롤백 (예측 취소)
// GameplayAbility에서 예측 설정
UMyAbility::UMyAbility()
{
// 로컬 예측 활성화
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::LocalPredicted;
// 인스턴스 정책 (예측에 필요)
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
}
// GameplayEffect에서 예측 고려
// Duration이 있는 이펙트는 예측이 롤백될 수 있음
// 쿨다운, 버프 등은 서버 확인 후 클라이언트에서 조정될 수 있음
데미지와 같은 Instant 이펙트는 예측하지 않는 것이 안전합니다. 서버에서만 실제 데미지를 적용하고, 클라이언트는 GameplayCue로 시각 피드백만 받습니다.
AttributeSet 복제
속성 값의 네트워크 동기화
UPROPERTY(BlueprintReadOnly, Category = "Health",
ReplicatedUsing = OnRep_Health)
FGameplayAttributeData Health;
// ReplicatedUsing: 값이 복제될 때 호출될 함수 지정
void UMyAttributeSet::GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// REPNOTIFY_Always: 값이 같아도 OnRep 호출
// 예측된 값이 서버 값으로 덮어쓰일 때 UI 업데이트 보장
DOREPLIFETIME_CONDITION_NOTIFY(
UMyAttributeSet,
Health,
COND_None,
REPNOTIFY_Always
);
DOREPLIFETIME_CONDITION_NOTIFY(
UMyAttributeSet,
MaxHealth,
COND_None,
REPNOTIFY_Always
);
// 마나, 공격력 등도 동일하게 설정
}
void UMyAttributeSet::OnRep_Health(const FGameplayAttributeData& OldHealth)
{
// GAS 매크로로 예측 처리
GAMEPLAYATTRIBUTE_REPNOTIFY(UMyAttributeSet, Health, OldHealth);
// 추가 로직 (UI 업데이트 등)
// 이 시점에서 Health.GetCurrentValue()는 서버에서 온 최신 값
}
이 매크로는 GAS의 예측 시스템과 통합되어, 예측된 값과 서버 값의 차이를 올바르게 처리합니다.
GameplayCue 복제
시각/청각 효과의 네트워크 전파
GameplayCue는 VFX, SFX 등 시각/청각 피드백을 담당합니다. Mixed/Minimal 모드에서도 모든 클라이언트에 전파됩니다.
// GameplayEffect에서 Cue 설정
// Details > Gameplay Cues
Gameplay Cue Tags:
- GameplayCue.Combat.Damage
- GameplayCue.Effect.Stun
// Cue는 세 가지 이벤트 지원:
// - OnActive: 이펙트 시작 시
// - WhileActive: 이펙트 지속 중 (Duration)
// - OnRemove: 이펙트 종료 시
// 서버에서 Cue 직접 실행 (모든 클라이언트에 전파)
void AMyActor::TriggerDamageCue(FVector HitLocation)
{
if (HasAuthority())
{
FGameplayCueParameters CueParams;
CueParams.Location = HitLocation;
CueParams.Normal = FVector::UpVector;
CueParams.EffectCauser = this;
// 모든 클라이언트에 Cue 전파
UAbilitySystemComponent* ASC = GetAbilitySystemComponent();
if (ASC)
{
ASC->ExecuteGameplayCue(
FGameplayTag::RequestGameplayTag(FName("GameplayCue.Combat.Damage")),
CueParams
);
}
}
}
// 로컬 전용 Cue (네트워크 전파 없음)
void AMyActor::TriggerLocalCue()
{
UAbilitySystemComponent* ASC = GetAbilitySystemComponent();
if (ASC)
{
ASC->ExecuteGameplayCueLocal(
FGameplayTag::RequestGameplayTag(FName("GameplayCue.UI.LevelUp")),
FGameplayCueParameters()
);
}
}
서버 권한 패턴
보안이 중요한 로직 처리
// 서버에서만 실행해야 하는 어빌리티
UMyServerOnlyAbility::UMyServerOnlyAbility()
{
// 서버에서만 실행 (클라이언트 예측 없음)
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::ServerOnly;
}
// 서버 권한 체크 패턴
void UMyAbility::ApplyCriticalEffect(AActor* Target)
{
// 항상 서버에서만 데미지 적용
if (!GetAvatarActorFromActorInfo()->HasAuthority())
{
return;
}
// 서버 권한 확인 후 실행
UAbilitySystemComponent* TargetASC =
UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(Target);
if (TargetASC)
{
FGameplayEffectSpecHandle Spec =
MakeOutgoingGameplayEffectSpec(DamageEffectClass, GetAbilityLevel());
ApplyGameplayEffectSpecToTarget(
CurrentSpecHandle,
CurrentActorInfo,
CurrentActivationInfo,
Spec,
TargetASC
);
}
}
// 치트 방지: 서버에서 조건 재검증
bool UMyAbility::CanActivateAbility(
const FGameplayAbilitySpecHandle Handle,
const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayTagContainer* SourceTags,
const FGameplayTagContainer* TargetTags,
FGameplayTagContainer* OptionalRelevantTags) const
{
if (!Super::CanActivateAbility(Handle, ActorInfo, SourceTags,
TargetTags, OptionalRelevantTags))
{
return false;
}
// 서버에서 추가 검증
if (ActorInfo->AvatarActor.IsValid() &&
ActorInfo->AvatarActor->HasAuthority())
{
// 쿨다운 조작 방지
// 비용 조작 방지
// 거리 제한 확인
}
return true;
}
- 데미지/힐은 항상 서버에서 적용
- 중요 조건은 서버에서 재검증
- 클라이언트는 시각 피드백만 담당
- 예측은 롤백 가능성을 고려하여 설계
핵심 요약
- Mixed 모드: 플레이어용, GE는 소유자만, Cues는 모두에게
- Minimal 모드: AI용, GE 복제 없음, 대역폭 최소화
- LocalPredicted로 클라이언트 예측 활성화, 롤백 대비
- REPNOTIFY_Always로 예측 값과 서버 값 동기화
- 데미지/중요 로직은 ServerOnly 또는 서버 권한 체크 필수
도전 과제
배운 내용을 직접 실습해보세요
AttributeSet의 UPROPERTY(Replicated)와 GetLifetimeReplicatedProps()를 구현하세요. DOREPLIFETIME_CONDITION으로 Owner만 또는 모든 클라이언트에게 복제하는 조건을 설정하세요.
어빌리티의 NetExecutionPolicy를 ServerOnly/LocalPredicted로 설정하고, 클라이언트에서 예측 실행 후 서버에서 확정하는 플로우를 테스트하세요. 네트워크 지연 시뮬레이션(NetEmulation)으로 예측의 효과를 확인하세요.
GameplayEffect의 복제 정책을 설정하고, Minimal Replication Proxy를 사용하여 GAS 네트워크 대역폭을 최적화하세요. MinimalReplicationTags를 설정하고 네트워크 트래픽을 Unreal Insights로 프로파일링하세요.