0. 개요

기존에는 HUD - PlayerController - PlayerState - GameMode 간 다량의 델리게이트를 통해서 뽑기 로직을 실행했으나, 이 과정에서 구조가 복잡해지고 콜백 함수의 증가로 인해 로직 파악에 어려움이 있었다.

이를 언리얼의 레플리케이션 시스템을 이용하여 단순화하고, 온라인에서 작동하도록 수정한다.

 

 

1. PlayerState에서 멤버 변수 레플리케이션 하기

이제 뽑는 로직은 서버인 GameMode에서 진행하고, 뽑은 카드를 멤버 변수로 저장하고 있다가 UI에서 노출하는 방법을 사용할 것이다.

이렇게 하면 멤버 변수를 레플리케이션 하도록 지정하고 콜백 함수를 활용하여 UI를 띄우는 방식의 구조로 변경할 수 있다.

 

(1) 뽑은 카드를 멤버 변수로 저장하기 - 레플리케이션으로 지정

	UPROPERTY(ReplicatedUsing = OnRep_UpgradeCardInfo)
	TArray<FAuraAbilityUpgradeInfo> ReplicatedCardInfo;
	UFUNCTION()
	void OnRep_UpgradeCardInfo();

 

(2) 델리게이트 선언

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCardsInitialized, TArray<FAuraAbilityUpgradeInfo>&, InitializedCards);

이 델리게이트는 HUD에서 바인딩할 것이며, 호출되면 뷰 포트에 위젯을 추가하고 카드의 뷰 모델들을 초기화한다.

	UPROPERTY()
	FOnCardsInitialized OnUpgradeCardsInitializedDelegate;

 

(2-1) AuraHUD - 델리게이트 바인딩

void AAuraHUD::InitOverlay(APlayerController *PC, APlayerState *PS, UAbilitySystemComponent *ASC, UAttributeSet *AS)
{
    // 위젯과 위젯 컨트롤러 생성
    checkf(OverlayWidgetClass, TEXT("Overlay Widget not Initialized. BP_AuraHUD"));
    checkf(OverlayWidgetControllerClass, TEXT("Overlay Widget Controller not Initialized. BP_AuraHUD"));

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    if (AAuraPlayerState* AuraPS = Cast<AAuraPlayerState>(PS))
    {
        AuraPS->OnUpgradeCardsInitializedDelegate.AddDynamic(this, &AAuraHUD::HandleRandomAbilityUpgradeInfos);
    }
}

 

 

 

(3) 레플리케이션 변수의 Setter 함수 내에서 델리게이트 호출

void SetUpgradeCardInfo(const TArray<FAuraAbilityUpgradeInfo>& NewCard);
void AAuraPlayerState::SetUpgradeCardInfo(const TArray<FAuraAbilityUpgradeInfo>& NewCard)
{
    ReplicatedCardInfo = NewCard;
    OnUpgradeCardsInitializedDelegate.Broadcast(ReplicatedCardInfo);
}

레플리케이션 변수의 정보를 저장하고 UI로 전달한다.

 

 

2. AuraGameMode에서 카드 뽑기

(1) PlayerController에서 서버로 카드 뽑기 요청하기 - 서버 RPC 함수

	UFUNCTION(Server, Reliable, BlueprintCallable)
	void Server_CreateCardSelection(AActor* InteractedActor);

 

void AAuraPlayerController::Server_CreateCardSelection_Implementation(AActor* InteractedActor)
{
    if (!HasAuthority())
        return;
    
    // 게임 모드에게 카드 뽑기를 요청
    if (AAuraGameModeBase* GameMode = Cast<AAuraGameModeBase>(GetWorld()->GetAuthGameMode()))
    {
        if (APawn* InteractedPawn = Cast<APawn>(InteractedActor))
        {
            if (APlayerController* PC = Cast<APlayerController>(InteractedPawn->GetController()))
            {
                // GameMode의 카드 뽑기 로직 호출
                GameMode->HandleInitializeCards(PC);
            }
        }
    }
}

위의 함수를 블루프린트나 C++ 코드 어디에선가 호출하면

void AAuraGameModeBase::HandleInitializeCards(APlayerController* PC)
{
	if (AAuraPlayerController* AuraPC = Cast<AAuraPlayerController>(PC))
	{
		if (AAuraPlayerState* AuraPS = AuraPC->GetPlayerState<AAuraPlayerState>())
		{
			auto RandomUpgradeInfos = GetRandomUpgradeInfosForActivatedAbility_Three(AuraPS);
			AuraPS->SetUpgradeCardInfo(RandomUpgradeInfos);

			// 카드 내 선택 버튼 브로드캐스트
			AuraPC->OnCardSelectedDelegate.Broadcast();
		}
	}
}

게임모드 내의 이 함수가 실행된다.

카드를 뽑는 메인 로직은 PlayerState의 함수를 그대로 GameMode로 가지고와 수정해서 사용한다.

 

    // 델리게이트 바인딩
    OnCardSelectedDelegate.AddUObject(this ,&AAuraPlayerController::HandleCardSelectionInitialized);

이 델리게이트는 카드 뽑기를 완료했을 때 호출하는 델리게이트이다.

void AAuraPlayerController::HandleCardSelectionInitialized()
{
    if (AAuraHUD* AuraHUD = Cast<AAuraHUD>(GetHUD()))
    {
        if (UMVVM_CardSelection* CardSelectionViewModel = AuraHUD->GetCardSelectionViewModel())
        {
            for (int32 i = 0; i < CardSelectionViewModel->GetNumCards(); ++i)
            {
                if (UMVVM_AbilityCard* CardViewModel = CardSelectionViewModel->GetCardViewModelByIndex(i))
                {
                    CardViewModel->OnUpgradeSelectedDelegate.AddDynamic(this, &AAuraPlayerController::HandleAbilityCardSelected);
                }
            }
        }
    }
}

모든 뷰 모델에 대해 선택 버튼에 업그레이드 적용 콜백 함수를 바인딩한다.

 

 

 

(2) 카드 뽑기 로직

	UFUNCTION()
	TArray<FAuraAbilityUpgradeInfo> GetRandomUpgradeInfosForActivatedAbility_Three(AAuraPlayerState* AuraPS);
TArray<FAuraAbilityUpgradeInfo> AAuraGameModeBase::GetRandomUpgradeInfosForActivatedAbility_Three(
	AAuraPlayerState* AuraPS)
{
    TArray<FGameplayTag> ActivatedAbilityTags = AuraPS->GetAllActiveAbilityTags();
    TArray<FGameplayTag> InActivatedAbilityTags;
	InActivatedAbilityTags.Empty();
    
    TArray<FAuraAbilityUpgradeInfo> RandomUpgradeInfos;
    RandomUpgradeInfos.Empty();

    // 글로벌 업그레이드 할당
    ActivatedAbilityTags.Add(FGameplayTag::RequestGameplayTag("Abilities.Fire"));
    ActivatedAbilityTags.Add(FGameplayTag::RequestGameplayTag("Abilities.Arcane"));
    ActivatedAbilityTags.Add(FGameplayTag::RequestGameplayTag("Abilities.Lightning"));

    // 어빌리티 습득 또는 레벨업 업그레이드 할당
    TArray<FGameplayTag> AllAbilitiesTags = FAuraGameplayTags::Get().GameplayAbilitiesTags;
    for (auto AbilityTag : AllAbilitiesTags)
    {
        InActivatedAbilityTags.AddUnique(AbilityTag);
    }
    
    // 주어진 어빌리티 태그에 대해 랜덤한 업그레이드 태그 뽑기
    if (UAbilityUpgradeInfo* Info = AbilityUpgradeInfo)
    {
        int ActivatedAbilityTagsNum = ActivatedAbilityTags.Num();
        int RandValue0 = UKismetMathLibrary::RandomIntegerInRange(0, ActivatedAbilityTagsNum-1);
        int RandValue1 = UKismetMathLibrary::RandomIntegerInRange(0, ActivatedAbilityTagsNum-1);
        int RandValue2 = UKismetMathLibrary::RandomIntegerInRange(0, ActivatedAbilityTagsNum-1);

        // 랜덤으로 뽑은 어빌리티 태그는 중복 가능 -> 파이어볼트 업그레이드 2개 노출 가능
        const auto Tag0 = ActivatedAbilityTags[RandValue0];
        const auto Tag1 = ActivatedAbilityTags[RandValue1];
        const auto Tag2 = ActivatedAbilityTags[RandValue2];
        
        FGameplayTag UpgradeTag0;
        FGameplayTag UpgradeTag1;
        FGameplayTag UpgradeTag2;

        // 루프 함수 제한
        int CurrentAttempt = 0;
        
        // 동일한 태그가 없을 때까지 뽑기
        while (UpgradeTag0 == UpgradeTag1 || UpgradeTag1 == UpgradeTag2 || UpgradeTag0 == UpgradeTag2)
        {
            UpgradeTag0 = Info->GetRandomUpgradeTagForAbility(Tag0);
            UpgradeTag1 = Info->GetRandomUpgradeTagForAbility(Tag1);
            UpgradeTag2 = Info->GetRandomUpgradeTagForAbility(Tag2);

            CurrentAttempt++;

            if (CurrentAttempt > 30)
                break;
        }

        RandomUpgradeInfos.Add(Info->GetUpgradeInfoForUpgradeTag(UpgradeTag0));
        RandomUpgradeInfos.Add(Info->GetUpgradeInfoForUpgradeTag(UpgradeTag1));
        RandomUpgradeInfos.Add(Info->GetUpgradeInfoForUpgradeTag(UpgradeTag2));
    }

    return RandomUpgradeInfos;
}

카드의 글로벌 데미지(또는 효과) 증가 업그레이드와 획득(또는 비획득)한 스펠 레벨업 카드, 보유한 어빌리티의 업그레이드 정보 가져오기 등 세 가지의 로직이 모두 섞여있기 때문에 추후에 분리해야 한다.