일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- C++
- 유니티
- rpc
- Unreal Engine
- unity
- Multiplay
- listen server
- 게임개발
- local prediction
- 언리얼엔진
- attribute
- gameplay tag
- Aegis
- stride
- MAC
- ability task
- 보안
- Replication
- gas
- level design
- 게임 개발
- photon fusion2
- os
- UI
- animation
- 언리얼 엔진
- widget
- gameplay ability system
- CTF
- gameplay effect
- Today
- Total
Replicated
[Drag Down] GameState에서 Player Idx 및 Ready 상태 관리하기 (PlayerState로 분리 예정) 본문
[Drag Down] GameState에서 Player Idx 및 Ready 상태 관리하기 (PlayerState로 분리 예정)
라구넹 2025. 5. 18. 19:56대기방에서 플레이어가 전부 Ready를 하면 게임을 시작할 수 있게 해야 한다
게임 전체의 상태와 관련된 것이니 GameState에서 관리하자
또한, 전체 유저가 다른 유저가 Ready인지 알 수 있도록 UI에서 보여줘야 한다
// 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(FPlayerReadyChanged);
/**
*
*/
UCLASS()
class DRAGDOWN_API ADDGameState : public AGameState
{
GENERATED_BODY()
public:
FPlayerReadyChanged OnPlayerReadyChanged;
void AddPlayer(const FString& UserName);
int32 GetPlayerIdx(const FString& UserName);
const TArray<FString>& GetPlayerNames() { return PlayerNames; }
const TArray<bool>& GetPlayerReadyStates() { return PlayerReadyStates; }
void SetPlayerReady(int32 PlayerIdx, bool bIsReady);
protected:
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
// TMap is not replicated
// Array Index is Player's Idx
protected:
UPROPERTY(Replicated)
TArray< FString > PlayerNames;
UPROPERTY(ReplicatedUsing = OnRep_PlayerReadyStates)
TArray< bool > PlayerReadyStates;
UFUNCTION()
void OnRep_PlayerReadyStates();
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "Game/DDGameState.h"
#include "Net/UnrealNetwork.h"
#include "DragDown.h"
void ADDGameState::AddPlayer(const FString& UserName)
{
UE_LOG(LogDD, Log, TEXT("ADDGameState - AddPlayer"));
for ( const FString& PlayerName : PlayerNames )
{
if ( PlayerName == UserName )
{
return;
}
}
PlayerNames.Emplace(UserName);
PlayerReadyStates.Emplace(false);
OnPlayerReadyChanged.Broadcast();
}
int32 ADDGameState::GetPlayerIdx(const FString& UserName)
{
int32 Length = PlayerNames.Num();
for (int32 i = 0; i < Length; ++i)
{
if ( PlayerNames[i] == UserName)
{
return i;
}
}
return -1;
}
void ADDGameState::SetPlayerReady(int32 PlayerIdx, bool bIsReady)
{
PlayerReadyStates[PlayerIdx] = bIsReady;
OnPlayerReadyChanged.Broadcast();
}
void ADDGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ADDGameState, PlayerNames);
DOREPLIFETIME(ADDGameState, PlayerReadyStates);
}
void ADDGameState::OnRep_PlayerReadyStates()
{
UE_LOG(LogDD, Log, TEXT("[NetMode: %d] OnRep_PlayerReadyStruct"), GetWorld()->GetNetMode());
OnPlayerReadyChanged.Broadcast();
}
처음엔 TMap으로 유저 ID를 키로 관리하려고 했는데,
TMap은 리플리케이션 되지 않는다
그냥 유저가 추가된 순서대로(Array들어간 순서대로) Idx를 가지도록 하였다
그리고 클라이언트는 그냥 리플리케이션 받아서 쓰자
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FPlayerReadyChanged);
이건 위젯 같은데서 구독해두고 변경 발생 시 이벤트 기반으로 업데이트되도록 함
플레이어 추가, 상태 변경, 리플리케이션 시 브로드캐스트
UCLASS()
class DRAGDOWN_API ADDPlayerController : public APlayerController
{
GENERATED_BODY()
...
// User Init
protected:
void HandleSetUserName(const FString& InUserName);
void SetUserName();
UFUNCTION(Server, Reliable)
void ServerSetUserName(const FString& InUserName);
void ADDPlayerController::HandleSetUserName(const FString& InUserName)
{
if ( !HasAuthority() ) return;
ADDGameState* GameState = Cast<ADDGameState>( GetWorld()->GetGameState() );
if ( GameState )
{
GameState->AddPlayer(InUserName);
UE_LOG(LogDD, Log, TEXT("[NetMode: %d] ADDPlayerController::HandleSetUserName - %s"), GetWorld()->GetNetMode(), *InUserName);
}
}
void ADDPlayerController::SetUserName()
{
UDDUserAuthSubsystem* UserAuthSubsystem = GetGameInstance()->GetSubsystem<UDDUserAuthSubsystem>();
if ( HasAuthority() )
{
HandleSetUserName(UserAuthSubsystem->GetUserName());
}
else
{
ServerSetUserName(UserAuthSubsystem->GetUserName());
}
}
void ADDPlayerController::ServerSetUserName_Implementation(const FString& InUserName)
{
if ( HasAuthority() )
{
HandleSetUserName(InUserName);
}
}
void ADDPlayerController::BeginPlayingState()
{
Super::BeginPlayingState();
if (IsLocalController())
{
InitGASWidget();
SetUserName();
}
}
GameState에서 클라이언트들은 RPC를 쓸 수가 없어(권한 없음) 컨트롤러에서 User Setting 필요
BeginPlayingState는 클라이언트에서만 발생하니, BeginPlayingState에서 유저 추가 설정
// 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));
}
}
void UDDPlayerReadyEntryWidget::UpdateReadyEntry(bool bIsReady)
{
if ( TxtReady )
{
if (bIsReady)
{
TxtReady->SetText(FText::FromString(ReadyStatement));
}
else
{
TxtReady->SetText(FText::FromString(NotReadyStatement));
}
}
}
유저 하나의 Ready 상태를 보여주기 위한 UI 엔트리
// 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 "UI/DDPlayerReadyEntryWidget.h"
#include "Components/VerticalBox.h"
#include "DragDown.h"
void UDDPlayerReadyListWidget::NativeConstruct()
{
GameState = Cast<ADDGameState>(GetWorld()->GetGameState());
UE_LOG(LogDD, Log, TEXT("UDDPlayerReadyListWidget NativeConstruct"));
if ( GameState )
{
GameState->OnPlayerReadyChanged.AddDynamic(this, &UDDPlayerReadyListWidget::UpdateReadyList);
}
UpdateReadyList();
}
void UDDPlayerReadyListWidget::UpdateReadyList()
{
if ( VerticalBox == nullptr ) return;
if ( GameState == nullptr ) return;
const TArray<FString>& PlayerNames = GameState->GetPlayerNames();
const TArray<bool>& PlayerReadyStates = GameState->GetPlayerReadyStates();
int Length = PlayerNames.Num();
for (int32 i = 0; i < Length; i++)
{
if (EntryMap.Contains(PlayerNames[i]))
{
bool bIsReady = PlayerReadyStates[i];
EntryMap[ PlayerNames[i] ]->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( PlayerNames[i] );
EntryMap.Emplace(PlayerNames[i], EntryWidget);
}
}
}
전체 유저의 상태를 보여주는 UI
GameState 델리게이트 구독하고 상태 변화 시 값을 읽어와서 업데이트
이미 전에 만든 적 있는 플레이어에 대한 UI는 그냥 업데이트만하고
새 유저에 대한 위젯은 생성
각각 유저 이름이랑 레디 상태가 잘 보임
다음은 Ready Button 만들어서 준비됐는지 안됐는지 체크할 수 있도록 해보자
* 그런데 생각해보니 PlayerState에서 각각의 상태를 관리하고 GameState는 전체 관리만 하는게 맞는 거 같다. 구조 변경하자
'언리얼 엔진 > Drag Down' 카테고리의 다른 글
[Drag Down] PlayerState에서 이름과 Ready 관리, GameState 전체 상태 관리 (0) | 2025.05.21 |
---|---|
[Drag Down] 게임 모드 및 컨트롤러 분리 (0) | 2025.05.19 |
[Drag Down] 플레이어 UI 기획 (각 플레이어 Z축 위치, 이름 표기) (0) | 2025.05.12 |
[Drag Down] 매치메이킹 UI (0) | 2025.05.12 |
[Drag Down] 자체 매치메이킹 연동 성공 (Unreal <-> Spring Boot) (0) | 2025.05.11 |