일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- Unreal Engine
- stride
- 게임개발
- 언리얼 엔진
- Security
- CTF
- gameplay ability
- MAC
- dtft
- gas
- DP
- gameplay effect
- sampling theory
- 운영체제
- Rr
- 메카님
- dirty cow
- DSP
- MLFQ
- linear difference equation
- reverse gravity
- 게임 개발
- 유니티
- ret2libc
- ability task
- frequency-domain spectrum analysis
- Race condition
- pdlc
- 언리얼엔진
- 유스케이스
- Today
- Total
다양한 기록
[ChronoSpace] ** Multiplay With GAS Setting (and Widget) ** 본문
[ChronoSpace] ** Multiplay With GAS Setting (and Widget) **
라구넹 2025. 1. 30. 09:35GAS나 Multiplay 같이 있는 예시가 그렇게 많진 않아 세팅하기 좀 어렵다
Lyra는 코드 자체가 좀 너무 많아 참고하려면 시간이 많이 걸릴 것이다
그래서 여기저기 뒤져보면서 코드 정리해서 만들어놨다
1. 기본 설정 (일반 멀티플레이 설정이랑 같고, GAS랑 관계 없이 기본 설정)
bReplicates = true 및 컴포넌트들에 리플리케이션 설정
이것만 해놔도 어느 정도는 리플리케이션이 된다
(ex. 스태틱 메시 컴포넌트 리플리케이트 설정 안하면 클라이언트에서 안보임)
2. Player Ability System 설정
// player cpp
void ACSCharacterPlayer::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
//UE_LOG(LogCS, Log, TEXT("[NetMode: %d] PossessedBy"), GetWorld()->GetNetMode());
SetASC();
SetGASAbilities();
}
PossessedBy는 서버에서만 실행됨 → 서버 ASC 설정
GiveAbility는 서버에서만 가능함, 서버 ASC 설정 이후에 진행
GiveAbility를 클라이언트에서 하다가 오류 났다
void ACSCharacterPlayer::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
// UE_LOG(LogCS, Log, TEXT("*** [NetMode : %d] OnRep_PlayerState, %s, %s"), GetWorld()->GetNetMode(), *GetName(), *GetPlayerState()->GetName());
SetASC();
EnergyBar->ActivateGAS();
// UE_LOG(LogCS, Log, TEXT("*** [NetMode : %d] OnRep_PlayerState, %s, %s"), GetWorld()->GetNetMode(), *GetName(), *GetPlayerState()->GetName());
}
클라이언트의 플레이어도 ASC를 가지긴 해야 함 (어트리뷰트 리플리케이션과 위젯 때문)
정확히는, ASC가 리플리케이션 되는데 그걸 가져와야 함
이 경우 OnRep_PlayerState에서 플레이어 스테이트가 리플리케이션 되면 어빌리티 시스템 설정을 해주고, 그 다음에 위젯의 필요 설정들을 해주어야 함 (서버의 위젯 설정은 BeginPlay에서 해줘야 함)
3. Widget 설정
// 위젯 cpp
#include "UI/CSGASUserWidget.h"
#include "AbilitySystemBlueprintLibrary.h"
void UCSGASUserWidget::SetOwner(AActor* InOwner)
{
if (IsValid(InOwner))
{
Owner = InOwner;
}
}
void UCSGASUserWidget::SetAbilitySystemComponent(AActor* InOwner)
{
if (IsValid(InOwner))
{
ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(InOwner);
}
}
UAbilitySystemComponent* UCSGASUserWidget::GetAbilitySystemComponent() const
{
return ASC;
}
서버와 클라이언트 플레이어의 ASC 설정 시점이 많이 다름
SwtOwner와 SetAbilitySystemComponent는 분리되어야 함
// 위젯 컴포넌트 cpp
void UCSGASWidgetComponent::InitWidget()
{
Super::InitWidget();
UE_LOG(LogCS, Log, TEXT("InitWidget IS Called"));
UCSGASUserWidget* GASUserWidget = Cast<UCSGASUserWidget>(GetWidget());
if (GASUserWidget)
{
GASUserWidget->SetOwner(GetOwner());
}
else
{
UE_LOG(LogCS, Log, TEXT("InitWidget Failed - No GASUserWidget"));
}
}
void UCSGASWidgetComponent::ActivateGAS()
{
UCSGASUserWidget* GASUserWidget = Cast<UCSGASUserWidget>(GetWidget());
if (GASUserWidget)
{
GASUserWidget->SetAbilitySystemComponent(GetOwner());
}
else
{
UE_LOG(LogCS, Log, TEXT("ActivateGAS Failed - No GASUserWidget"));
}
}
InitWidget의 발동 시점
리슨 서버: PossessedBy → InitWidget → BeginPlay
클라이언트: InitWidget → BeginPlay → PossessedBy
InitWIdget에서 어빌리티 컴포넌트를 설정하게 되면 서버나 클라이언트에서 위젯 ASC 연결 문제가 생김
위에서 함수 분리해 뒀던 이유가 이것
하위 위젯 컴포넌트는 내버려둬도 되는데, 이유는 GAS의 어트리뷰트가 Effect로 변경되면 알아서 리플리케이션 되기 때문
void ACSCharacterPlayer::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
//UE_LOG(LogCS, Log, TEXT("[NetMode: %d] PossessedBy"), GetWorld()->GetNetMode());
SetASC();
SetGASAbilities();
}
void ACSCharacterPlayer::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogCS, Log, TEXT("[NetMode: %d] BeginPlay"), GetWorld()->GetNetMode());
if ( HasAuthority() )
{
EnergyBar->ActivateGAS();
}
if (!IsLocallyControlled())
{
//EnergyBar->SetVisibility(false);
return;
}
// 이 밑으론 로컬 컨트롤러만 진입 가능
APlayerController* PlayerController = CastChecked<APlayerController>(GetController());
if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
{
Subsystem->AddMappingContext(MappingContext, 0);
}
//UE_LOG(LogCS, Log, TEXT("[NetMode: %d] BeginPlay"), GetWorld()->GetNetMode());
}
ActivateGAS의 실행 시점
서버: PossessedBy
클라이언트: BeginPlay
4. 어빌리티 설정
- 어빌리티 발동 RPC 및 설정
클라이언트는 입력을 받기만 하고, 실제 실행은 서버에서 되어야 함
근데 클라이언트도 GiveAbility만 안되는 거지 ActivateAbility 되는데, 이에 문제가 있음
UCSGA_WhiteHall::UCSGA_WhiteHall()
{
NetExecutionPolicy = EGameplayAbilityNetExecutionPolicy::ServerOnly;
InstancingPolicy = EGameplayAbilityInstancingPolicy::InstancedPerActor;
}
NetExecutionPolicy를 서버 온리로 설정해주지 않으면 서버와 클라이언트 둘 다 실행되버린다
그러다보니, 액터를 스폰하는 어빌리티의 경우 액터가 총 두 개 생성된다
저걸 해줘야 서버에서만 실행된다
// h
// Input Pressed RPC **************************************
void GASInputPressed(int32 InputId);
UFUNCTION(Server, Reliable)
void ServerGASInputPressed(int32 InputId);
void HandleGASInputPressed(int32 InputId);
// ********************************************************
// cpp
void ACSCharacterPlayer::GASInputPressed(int32 InputId)
{
if ( HasAuthority() )
{
//UE_LOG(LogCS, Log, TEXT("[NetMode : %d], GASInputPressed"), GetWorld()->GetNetMode());
HandleGASInputPressed(InputId);
}
else
{
//UE_LOG(LogCS, Log, TEXT("[NetMode : %d], GASInputPressed"), GetWorld()->GetNetMode());
ServerGASInputPressed(InputId);
}
}
void ACSCharacterPlayer::ServerGASInputPressed_Implementation(int32 InputId)
{
//UE_LOG(LogCS, Log, TEXT("[NetMode : %d], ServerGASInputPressed_Implementation"), GetWorld()->GetNetMode());
if ( HasAuthority() )
{
HandleGASInputPressed(InputId);
}
}
void ACSCharacterPlayer::HandleGASInputPressed(int32 InputId)
{
if ( !ASC )
{
return;
}
FGameplayAbilitySpec* Spec = ASC->FindAbilitySpecFromInputID(InputId);
if (Spec)
{
if (Spec->InputPressed) return;
UE_LOG(LogCS, Log, TEXT("[NetMode : %d], HandleGASInputPressed"), GetWorld()->GetNetMode());
Spec->InputPressed = true;
if (Spec->IsActive())
{
ASC->AbilitySpecInputPressed(*Spec);
}
else
{
ASC->TryActivateAbility(Spec->Handle);
}
}
}
서버는 그냥 실행하고 클라이언트는 서버로 RPC를 날리는 형태의 코드
클라이언트는 딱 입력만 받는다
(사실 클라이언트에서 그냥 어빌리티 Activate하는 식으로 구현해도 되긴 하는데,
가능하면 클라이언트는 입력 받기 + 렌더링만 하도록)
- 타겟 액터 RPC
타겟 액터에서 RPC를 써줘야 하는 경우가 있다
메테리얼 채도 조정은 RPC를 써줘야 적용된다
void UCSAT_WeakenGravityBox::SpawnAndInitializeTargetActor()
{
SpawnedTargetActor = Cast<ACSTA_WeakenGravityBox>(GetWorld()->SpawnActorDeferred<ACSTA_WeakenGravityBox>(TargetActorClass, FTransform::Identity, nullptr, nullptr, ESpawnActorCollisionHandlingMethod::AlwaysSpawn));
if (SpawnedTargetActor)
{
SpawnedTargetActor->SetOwner(GetOwnerActor());
SpawnedTargetActor->OnComplete.AddDynamic(this, &UCSAT_WeakenGravityBox::OnTargetActorReadyCallback);
SpawnedTargetActor->SetGravityCoef(GravityCoef);
}
}
타겟 액터는 기본적으로 오너가 없어서 RPC가 제대로 안써진다
어빌리티 태스크에서 SetOwner 해줘야 한다
// h
UFUNCTION(NetMulticast, Reliable)
void NetMulticastSaturationSetting(float InGravityCoef);
void SaturationSetting();
void HandleSaturationSetting(float InGravityCoef);
// cpp
void ACSTA_WeakenGravityBox::NetMulticastSaturationSetting_Implementation(float InGravityCoef)
{
HandleSaturationSetting(InGravityCoef);
}
void ACSTA_WeakenGravityBox::SaturationSetting()
{
if ( HasAuthority() )
{
NetMulticastSaturationSetting(GravityCoef);
}
}
void ACSTA_WeakenGravityBox::HandleSaturationSetting(float InGravityCoef)
{
UE_LOG(LogCS, Log, TEXT("[NetMode : %d] HandleSaturationSetting, %f"), GetWorld()->GetNetMode(), GravityCoef);
UMaterialInstanceDynamic* DynMaterial = Cast<UMaterialInstanceDynamic>(StaticMeshComp->GetMaterial(0));
FLinearColor OrgColor;
DynMaterial->GetVectorParameterValue(FName(TEXT("Color")), OrgColor);
// r:h g:s b:v a:a
FLinearColor HSVColor = OrgColor.LinearRGBToHSV();
HSVColor.G *= InGravityCoef;
DynMaterial->SetVectorParameterValue(FName(TEXT("Color")), HSVColor.HSVToLinearRGB());
}
NetMulticast 쓰면 클라이언트 전부에 날아간다
NetMulticast 쓰면 리슨 서버랑 클라이언트 전부에 날아간다
즉, 서버에서 저 함수를 사용해도 서버 본인에서도 실행된다
단, GravityCoef는 에디터에서 변경한 값인데, 클라이언트는 에디터에서 변경한 값을 읽지 못하는데 의도된 사항인지 버그인지는 모르겠고 RPC 인자에 추가해줘서 해결함
결과
서버에서 위젯이 둘 다 안보이는지 이유
언리얼 5.5.1 버그였다
나랑 똑같은 문제를 겪은 사람들이 많았다
'언리얼 엔진 > ChronoSpace' 카테고리의 다른 글
[ChronoSpace] Gravity Core / Gravity Direction 조정을 통한 Custom Gravity (0) | 2025.01.26 |
---|---|
[ChronoSpace] Weaken Gravity / Material Saturation Setting (0) | 2025.01.26 |
[ChronoSpace] Black Hole & White Hole (0) | 2025.01.26 |
[ChronoSpace] 밀리는 Character (0) | 2025.01.25 |
[ChronoSpace] Static Mesh Scale과 관계없는 Meterial (0) | 2025.01.22 |