StackableAbility 관련 네트워크 최적화

김지윤·2025년 8월 13일
0

UE5_GAS

목록 보기
20/22
post-thumbnail

기존엔 StackableAbilityComponent의 내부 변수인 AbilityStacks를 OwnerOnly로 Replicate해서 클라이언트가 알 수 있도록 했다. 하지만 생각해보니 이 Component 자체가 모두에게 Replicate되고 있었다. 그래서 이걸 개선하기로 했다.

내 생각엔..

자기 자신의 스킬은 즉각 반응 정말 중요하고, HUD에 항상 표시되기 때문에 Replicate하는 게 무조건 맞다. 하지만 다른 사람의 스킬은 볼 일 자체가 없을 수도 있고, 본다 해도 그리 자주 보진 않는 경우가 많다. 따라서 이 Component를 Owner 클라이언트에게만 Replicate되도록 수정하기로 했다. 필요하다면 RPC를 통해 구성하는 게 맞는 것 같다.

Component -> Actor

이런저런 시도를 해봤는데, 큰 구조 개선까지 필요한 일은 아니였다. 일단 첫 번째로 ActorComponent를 상속받던 상태에서 Actor를 상속받도록 바꿨다. Component는 부모 액터의 Replicate 범위를 그대로 따라가기 때문에, '딱 Owner에게만' Replicate하는 방법은 없다. 때문에 Component가 아닌 독립적인 Replicate 방식을 가지는 객체가 필요했다. 따라서 StackableAbilityComponent의 이름을 StackableAbilityManager로 바꿨다.

AvatarActor -> ASC

그 뒤 기존 AvatarActor에게 붙어있던 상태를 ASC가 소유하도록 변경했다. 어차피 Ability를 관리하는 클래스기 때문에 가독성이 올라간 느낌이다.

UPROPERTY(Replicated)
TObjectPtr<AStackableAbilityManager> StackableAbilityManager;

DOREPLIFETIME_CONDITION(UAuraAbilitySystemComponent, StackableAbilityManager, COND_OwnerOnly);

여기서 중요한 건 OwnerOnly로 설정한 부분이다.

StackableAbilityManager 생성 로직

AStackableAbilityManager* UStackableAbility::GetStackableAbilityManager(const FGameplayAbilityActorInfo* ActorInfo) const
{
	UAuraAbilitySystemComponent* ASC = Cast<UAuraAbilitySystemComponent>(ActorInfo->AbilitySystemComponent);

	return ASC->GetStackableAbilityManager();
}

먼저 StackableAbility 클래스가 직접 Component를 생성하던 로직을 이렇게 변경했다. 호출하면 그냥 ASC한테 달라고 요청한다.

AStackableAbilityManager* UAuraAbilitySystemComponent::GetStackableAbilityManager()
{
	if (!StackableAbilityManager && IsOwnerActorAuthoritative())
	{
		// 서버에서만 생성하고, 그 후 PlayerController에게 붙여 Owner 클라이언트에게만 Replicate됩니다.
		// AI인 경우에도 해당 객체가 필요하므로 주의합니다.
		FActorSpawnParameters Params;
		Params.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;

		if (APlayerController* PlayerController = AbilityActorInfo->PlayerController.Get())
		{
			Params.Owner = PlayerController;
		}

		AActor* Avatar = GetAvatarActor();
		
		StackableAbilityManager = GetWorld()->SpawnActor<AStackableAbilityManager>(AStackableAbilityManager::StaticClass(),
			Avatar ? Avatar->GetActorLocation() : FVector::ZeroVector,
			Avatar ? Avatar->GetActorRotation() : FRotator::ZeroRotator,
			Params);

		if (StackableAbilityManager && Avatar && Avatar->GetRootComponent())
		{
			StackableAbilityManager->AttachToComponent(Avatar->GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
		}
	}
	return StackableAbilityManager;
}

그 다음 ASC는 멤버변수로 참조하고 있는 StackableAbilityManager를 반환해주는데, 이 과정에서 객체가 존재하지 않을 경우 스폰한다. 여기서 중요한 부분은 Owner를 PlayerController로 설정한다는 거다. 이래야만 Owner 클라이언트에게만 복제하는 게 가능하다.

bOnlyRelevantToOwner = true;

위 구문은 StackableAbilityManager의 생성자에서 값을 초기화하는 구문이다. 이 값이 중요하다. 이게 true여야만 Actor를 OwnerOnly로 복제가 가능하며, 이 액터의 Owner가 PlayerController여야만 그 Owner 클라이언트에게 복제된다. PlayerController가 클라이언트별 넷 연결을 직접 보유하고 있기 때문이다.

그 뒤, Actor 자체가 AvatarActor에게 붙어있어야 자연스러우므로 Attach해줬다. 별로 중요한 부분은 아니다.

AStackableAbilityManager::AStackableAbilityManager()
{
	SetReplicates(true);
	PrimaryActorTick.bCanEverTick = false;
	// Owner 클라이언트에게만 복제합니다.
	bOnlyRelevantToOwner = true;
	// 위치/거리 신경 안 쓰고 Owner Relevancy를 따릅니다.
	bNetUseOwnerRelevancy = true;
	SetReplicateMovement(false);
	SetActorEnableCollision(false);
	// 초기 전송을 보장합니다.
	SetNetDormancy(DORM_Awake);

	AbilityStacks.OwnerActor = this;
}

StackableAbilityManager의 생성자다. Replicates 자체는 true지만 Owner에게만 복제할 수 있도록 설정해줬다.

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

	if (HasAuthority())
	{
		UE_LOG(LogTemp, Warning, TEXT("Server: AStackableAbilityManager::BeginPlay"));
	}
	else
	{
		if (APlayerController* PlayerController = Cast<APlayerController>(GetOwner()))
		{
			if (PlayerController->IsLocalController())
			{
				UE_LOG(LogTemp, Warning, TEXT("Client, Mine: AStackableAbilityManager::BeginPlay"));
			}
			else
			{
				UE_LOG(LogTemp, Warning, TEXT("Client, NotMine: AStackableAbilityManager::BeginPlay"));
			}
		}
	}
}

그 뒤 제대로 확인하기 위해 BeginPlay에서 로그를 찍어봤다. 서버는 Manager 개수만큼 로그가 나올 거고, 클라이언트는 Mine으로 딱 1개만 나와야 정상이다.

성공! 클라이언트의 UI도 정상작동하는 걸 보니 제대로 Replicate된 것 같다.

profile
공부한 거 시간 날 때 작성하는 곳

0개의 댓글