일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
Tags
- listen server
- photon fusion2
- widget
- 게임 개발
- ability task
- MAC
- gameplay tag
- gas
- Unreal Engine
- gameplay ability system
- local prediction
- Replication
- Multiplay
- stride
- unity
- 언리얼 엔진
- level design
- animation
- Aegis
- 보안
- 언리얼엔진
- 유니티
- C++
- gameplay effect
- UI
- rpc
- attribute
- os
- CTF
- 게임개발
Archives
- Today
- Total
Replicated
[Drag Down] PlayerState에서 이름과 Ready 관리, GameState 전체 상태 관리 본문
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerState.h"
#include "AbilitySystemInterface.h"
#include "DDPlayerState.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FPlayerInfoChanged);
/**
*
*/
UCLASS()
class DRAGDOWN_API ADDPlayerState : public APlayerState, public IAbilitySystemInterface
{
GENERATED_BODY()
public:
ADDPlayerState();
virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;
FPlayerInfoChanged OnPlayerInfoChanged;
protected:
UPROPERTY(EditAnywhere, Category = GAS)
TObjectPtr<class UAbilitySystemComponent> ASC;
UPROPERTY()
TObjectPtr<class UDDAttributeSet> AttributeSet;
public:
FORCEINLINE const FString& GetUserName() { return UserName; }
protected:
virtual void BeginPlay() override;
UFUNCTION(Server, Reliable)
void ServerSetUserName(const FString& InUserName);
protected:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UPROPERTY(ReplicatedUsing = OnRep_UserName)
FString UserName;
UFUNCTION()
void OnRep_UserName();
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Player/DDPlayerState.h"
#include "AbilitySystemComponent.h"
#include "Attribute/DDAttributeSet.h"
#include "Net/UnrealNetwork.h"
#include "Subsystem/DDUserAuthSubsystem.h"
ADDPlayerState::ADDPlayerState()
{
ASC = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC"));
ASC->SetIsReplicated(true);
AttributeSet = CreateDefaultSubobject<UDDAttributeSet>(TEXT("AttributeSet"));
}
void ADDPlayerState::ServerSetUserName_Implementation(const FString& InUserName)
{
if ( HasAuthority() )
{
UserName = InUserName;
OnPlayerInfoChanged.Broadcast();
}
}
UAbilitySystemComponent* ADDPlayerState::GetAbilitySystemComponent() const
{
return ASC;
}
void ADDPlayerState::BeginPlay()
{
Super::BeginPlay();
UDDUserAuthSubsystem* UserAuthSubsystem = GetGameInstance()->GetSubsystem<UDDUserAuthSubsystem>();
if (UserAuthSubsystem == nullptr) return;
if (HasAuthority())
{
UserName = UserAuthSubsystem->GetUserName();
OnPlayerInfoChanged.Broadcast();
}
else
{
ServerSetUserName(UserAuthSubsystem->GetUserName());
}
}
void ADDPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ADDPlayerState, UserName);
}
void ADDPlayerState::OnRep_UserName()
{
OnPlayerInfoChanged.Broadcast();
}
플레이어 스테이트
UserName을 서버에서 등록하고, 클라이언트는 리플리케이션 받아 사용
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Player/DDPlayerState.h"
#include "DDWaitingPlayerState.generated.h"
/**
*
*/
UCLASS()
class DRAGDOWN_API ADDWaitingPlayerState : public ADDPlayerState
{
GENERATED_BODY()
public:
ADDWaitingPlayerState();
FORCEINLINE bool IsUserReady() { return bIsUserReady; }
void SetUserReady(bool bIsReady);
protected:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UFUNCTION(Server, Reliable)
void ServerSetUserReady(bool bIsReady);
UPROPERTY(ReplicatedUsing = OnRep_bIsUserReady)
bool bIsUserReady;
UFUNCTION()
void OnRep_bIsUserReady();
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Player/DDWaitingPlayerState.h"
#include "Net/UnrealNetwork.h"
ADDWaitingPlayerState::ADDWaitingPlayerState()
{
bIsUserReady = false;
}
void ADDWaitingPlayerState::SetUserReady(bool bIsReady)
{
if ( HasAuthority() )
{
bIsUserReady = bIsReady;
OnPlayerInfoChanged.Broadcast();
}
else
{
ServerSetUserReady(bIsReady);
}
}
void ADDWaitingPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ADDWaitingPlayerState, bIsUserReady);
}
void ADDWaitingPlayerState::ServerSetUserReady_Implementation(bool bIsReady)
{
if ( HasAuthority() )
{
bIsUserReady = bIsReady;
OnPlayerInfoChanged.Broadcast();
}
}
void ADDWaitingPlayerState::OnRep_bIsUserReady()
{
OnPlayerInfoChanged.Broadcast();
}
대기방에서 사용하는 PlayerState
Ready 관리, 변경은 RPC 처리
각각의 PlayerState의 OnPlayerInfoChanged를 GameState에서 구독하고, UI들은 GameState의 OnGameInfoChanged를 구독하여 변경 시 업데이트
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameState.h"
#include "DDGameState.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FGameInfoChanged);
/**
*
*/
UCLASS()
class DRAGDOWN_API ADDGameState : public AGameState
{
GENERATED_BODY()
public:
FGameInfoChanged OnGameInfoChanged;
protected:
virtual void AddPlayerState(APlayerState* PlayerState) override;
UFUNCTION()
void OnPlayerInfoChangedCallback();
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Game/DDGameState.h"
#include "Net/UnrealNetwork.h"
#include "Player/DDPlayerState.h"
#include "DragDown.h"
void ADDGameState::AddPlayerState(APlayerState* PlayerState)
{
Super::AddPlayerState(PlayerState);
UE_LOG(LogDD, Log, TEXT("[NetMode: %d] ADDGameState::AddPlayerState, %d"), GetWorld()->GetNetMode(), PlayerArray.Num());
for ( const auto& PS : PlayerArray )
{
if ( ADDPlayerState* DDPS = Cast<ADDPlayerState>(PS) )
{
DDPS->OnPlayerInfoChanged.Clear();
DDPS->OnPlayerInfoChanged.AddDynamic(this, &ADDGameState::OnPlayerInfoChangedCallback);
}
}
}
void ADDGameState::OnPlayerInfoChangedCallback()
{
UE_LOG(LogDD, Log, TEXT("ADDGameState::OnPlayerInfoChangedCallback"));
OnGameInfoChanged.Broadcast();
}
PlayerArray를 통해 각각의 PlayerState에 접근하고 구독함
리플리케이션 받으면 브로드캐스트
// Fill out your copyright notice in the Description page of Project Settings.
#include "Game/DDWaitingGameState.h"
#include "Player/DDWaitingPlayerState.h"
bool ADDWaitingGameState::AreAllPlayerReady()
{
if (!HasAuthority()) return false;
for (const auto& PS : PlayerArray)
{
ADDWaitingPlayerState* WaitingPS = Cast<ADDWaitingPlayerState>(PS);
if (!WaitingPS->IsUserReady())
{
return false;
}
}
return true;
}
WaitingGameState에서는 모든 유저가 Ready인지 체크해주는 함수 보유
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "DDPlayerReadyEntryWidget.generated.h"
/**
*
*/
UCLASS()
class DRAGDOWN_API UDDPlayerReadyEntryWidget : public UUserWidget
{
GENERATED_BODY()
public:
void InitReadyEntry(const FString& UserName);
void UpdateReadyEntry(bool bIsReady);
protected:
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UTextBlock> TxtUserName;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UTextBlock> TxtReady;
const FString ReadyStatement = "Ready";
const FString NotReadyStatement = "Not Ready";
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/DDPlayerReadyEntryWidget.h"
#include "Components/TextBlock.h"
void UDDPlayerReadyEntryWidget::InitReadyEntry(const FString& UserName)
{
if ( TxtUserName )
{
TxtUserName->SetText( FText::FromString( UserName ) );
}
if ( TxtReady )
{
TxtReady->SetText(FText::FromString(NotReadyStatement));
FSlateColor NewColor = FSlateColor(FLinearColor::Red);
TxtReady->SetColorAndOpacity(NewColor);
}
}
void UDDPlayerReadyEntryWidget::UpdateReadyEntry(bool bIsReady)
{
if ( TxtReady )
{
if (bIsReady)
{
TxtReady->SetText(FText::FromString(ReadyStatement));
FSlateColor NewColor = FSlateColor(FLinearColor::Green);
TxtReady->SetColorAndOpacity(NewColor);
}
else
{
TxtReady->SetText(FText::FromString(NotReadyStatement));
FSlateColor NewColor = FSlateColor(FLinearColor::Red);
TxtReady->SetColorAndOpacity(NewColor);
}
}
}
하나의 유저 정보를 담는 엔트리
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "DDPlayerReadyListWidget.generated.h"
/**
*
*/
UCLASS()
class DRAGDOWN_API UDDPlayerReadyListWidget : public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
UFUNCTION(BlueprintCallable)
void UpdateReadyList();
protected:
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UVerticalBox> VerticalBox;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSubclassOf<class UDDPlayerReadyEntryWidget> EntryWidgetToList;
TMap<FString, TObjectPtr<class UDDPlayerReadyEntryWidget>> EntryMap;
TObjectPtr<class ADDGameState> GameState;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/DDPlayerReadyListWidget.h"
#include "Game/DDGameState.h"
#include "Player/DDWaitingPlayerState.h"
#include "UI/DDPlayerReadyEntryWidget.h"
#include "Components/VerticalBox.h"
#include "DragDown.h"
void UDDPlayerReadyListWidget::NativeConstruct()
{
UE_LOG(LogDD, Log, TEXT("[NetMode: %d] NativeConstruct"), GetWorld()->GetNetMode());
GameState = Cast<ADDGameState>(GetWorld()->GetGameState());
if ( GameState )
{
GameState->OnGameInfoChanged.AddDynamic(this, &UDDPlayerReadyListWidget::UpdateReadyList);
}
if ( !GetWorld()->GetFirstPlayerController()->HasAuthority() )
{
UpdateReadyList();
}
}
void UDDPlayerReadyListWidget::UpdateReadyList()
{
if ( VerticalBox == nullptr ) return;
if ( GameState == nullptr ) return;
for ( const auto& PS : GameState->PlayerArray )
{
ADDWaitingPlayerState* WaitingDDPS = Cast<ADDWaitingPlayerState>(PS);
if (WaitingDDPS == nullptr) return;
if ( EntryMap.Contains( WaitingDDPS->GetUserName() ) )
{
bool bIsReady = WaitingDDPS->IsUserReady();
EntryMap[WaitingDDPS->GetUserName()]->UpdateReadyEntry(bIsReady);
}
else
{
if (EntryWidgetToList == nullptr)
{
UE_LOG(LogDD, Log, TEXT("UDDPlayerReadyListWidget - EntryWidgetToList Is Null"));
return;
}
UDDPlayerReadyEntryWidget* EntryWidget = NewObject<UDDPlayerReadyEntryWidget>(this, EntryWidgetToList);
if (EntryWidget == nullptr) return;
VerticalBox->AddChild(EntryWidget);
EntryWidget->InitReadyEntry(WaitingDDPS->GetUserName());
EntryMap.Emplace(WaitingDDPS->GetUserName(), EntryWidget);
}
}
}
전체 유저 정보 관리하는 위젯
GameState에서 변경에 대한 이벤트가 발생 시 업데이트
* NativeConstruct 시점 문제(클라이언트는 NativeConstruct 시점이 많이 느려서 PlayerState추가된 다음 바인딩됨 -> 알아서 브로드캐스트가 안됨)로 인해 로컬에서는 NativeConstruct 후 UpdateReadyList 호출
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "DDReadyButtonWidget.generated.h"
/**
*
*/
UCLASS()
class DRAGDOWN_API UDDReadyButtonWidget : public UUserWidget
{
GENERATED_BODY()
public:
virtual void NativeConstruct() override;
UFUNCTION()
void OnReadyButtonClick();
protected:
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UButton> BtnReady;
UPROPERTY(meta = (BindWidget))
TObjectPtr<class UTextBlock> TxtReady;
UPROPERTY()
TObjectPtr<class ADDWaitingPlayerState> WatingPS;
bool bIsReady;
const FString ReadyStatement = "Ready";
const FString NotReadyStatement = "Not Ready";
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "UI/DDReadyButtonWidget.h"
#include "Components/Button.h"
#include "Components/TextBlock.h"
#include "Player/DDWaitingPlayerState.h"
void UDDReadyButtonWidget::NativeConstruct()
{
Super::NativeConstruct();
bIsReady = false;
APlayerController* PC = GetWorld()->GetFirstPlayerController();
if ( PC )
{
WatingPS = PC->GetPlayerState<ADDWaitingPlayerState>();
}
if ( BtnReady )
{
BtnReady->OnClicked.AddDynamic(this, &UDDReadyButtonWidget::OnReadyButtonClick);
}
if ( TxtReady )
{
TxtReady->SetText(FText::FromString(NotReadyStatement));
}
}
void UDDReadyButtonWidget::OnReadyButtonClick()
{
if ( WatingPS )
{
bIsReady = !bIsReady;
WatingPS->SetUserReady(bIsReady);
}
if ( TxtReady )
{
if ( bIsReady )
{
TxtReady->SetText( FText::FromString( ReadyStatement ) );
}
else
{
TxtReady->SetText(FText::FromString( NotReadyStatement ));
}
}
}
레디 버튼
이런식으로 구성됨
GameState에서 Ready 다 관리하는 걸로 짰다가 고치느라 시간이 꽤 소모된 거 같다
시작하기 전 좀 더 고민해서 작업하자
'언리얼 엔진 > Drag Down' 카테고리의 다른 글
** [Drag Down] 채팅 기능 구현 (Chatting) ** (0) | 2025.05.24 |
---|---|
[Drag Down] 맵 이동 포탈 (0) | 2025.05.21 |
[Drag Down] 게임 모드 및 컨트롤러 분리 (0) | 2025.05.19 |
[Drag Down] GameState에서 Player Idx 및 Ready 상태 관리하기 (PlayerState로 분리 예정) (0) | 2025.05.18 |
[Drag Down] 플레이어 UI 기획 (각 플레이어 Z축 위치, 이름 표기) (0) | 2025.05.12 |