다양한 기록

[ChronoSpace] SetGravityDirection 동기화 문제 (Pitch 동기화) 본문

언리얼 엔진/ChronoSpace

[ChronoSpace] SetGravityDirection 동기화 문제 (Pitch 동기화)

라구넹 2025. 2. 3. 18:37

 

 

 

서버 말고는 GravityDirection이 동기화가 안되어 있었다

CharacterMovementComponent는 위치나 가속도 등의 핵심 정보만 동기화한다고 함

 

 

방법1. 다시 캐릭터로 중력 세팅 책임 옮기기

이건 그냥 코드가 마음에 안듦

이걸 Character의 OnActorBeginOverlap이나 Tick에서 처리하기 싫음

기각

 

방법2. RPC로 GravityDirection 세팅시키기

Tick이든 Timer든, 짧은 간격으로 RPC 날리는 건 트래픽이 너무 많아져서 싫음

포톤 퓨전2에서 Update에 넣어두고 RPC 너무 날려서 게임 꺼진 적 있어서 꺼려짐

기각

 

방법3. 게임플레이 어빌리티로 만들어서 Gravity Core에 들러붙는 능력으로 만들기

Character 클래스의 책임도 덜해지고,

각 캐릭터를 중심으로 이루어지는 거라 Replicated 옵션 사용 가능

 

조금 마음에 안드는 건, 이전에는 모든 캐릭터를 끌어당겼는데 이제 이 어빌리티 및 ASC가 있어야 됨

일반 캐릭터로 만든 상호작용 용도의 오브젝트는 중력 코어에는 적용되지 않음

어트리뷰트에서 처리하려 그랬는데, 어트리뷰트는 float만 된다

 

그런데 여기서 좀 큰 문제

Gameplay Ability를 Server에서만 작동하게 할 것인데,

이러면 리플리케이션이 작동하지 않는다

 

그렇다고 서버에서만 작동하게 하는 규칙을 깨고 싶진 않음

기각

 

방법4.

결국 RPC 쓰기 싫고(사실상 하면 안됨), 어트리뷰트에서 이펙트로 설정하는 것도 안되면(X, Y, Z 따로 하면 되는데 코드가 너무 더러워져서 이렇게 할 바에 플레이어에 다 넣음) 결국 프로퍼티 리플리케이션을 이용하는 방법밖에 없음

프로퍼티 리플리케이션을 이용하려면

액터를 이용: 이미 캐릭터가 액터 파생인데, 캐릭터에 붙이기 싫어서 방법 찾는 중

액터에 속하는 액터컴포넌트를 이용 : 이게 좋아보임

 

근데 하다 보니까 Pitch만 동기화가 안됨

기본적으로 언리얼 엔진은 Yaw만 리플리케이션 해준다고 함

근데 정확히는, Direction 설정이 안되는 것 같지는 않음

 

잘보면 순간적으로 바뀜

로그도 동기화될 때가 분명히 보임

한번 바뀌었다가 새 값으로 덮어쓰기 당하고 있는 상태로 보임

 

로그도 찾으니까 분명히 있긴 함

 

Pitch가 0으로 덮어씌워지는 순간이 분명히 존재함

 

서버에서 Pitch를 0으로 만드려고 하는 듯 함

 

이거 해줘야 함

 

 

 

 

코드

// h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "Components/ActorComponent.h"
#include "CSCustomGravityDirComponent.generated.h"


UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) )
class CHRONOSPACE_API UCSCustomGravityDirComponent : public UActorComponent
{
	GENERATED_BODY()

public:	
	UCSCustomGravityDirComponent();

protected:
	virtual void BeginPlay() override;
	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

	FVector GetDirection();

	UPROPERTY()
	TObjectPtr<class ACSGravityCore> CurrentGravityCore;

	UPROPERTY(ReplicatedUsing = OnRep_CurrentGravityDirection)
	FVector CurrentGravityDirection;

	UFUNCTION()
	void OnRep_CurrentGravityDirection();

	UPROPERTY()
	TObjectPtr<class ACharacter> OwnerCharacter;

	FVector OrgGravityDirection;

public:	
	virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override;

protected:
	UFUNCTION()
	void OnActorBeginOverlapCallback(AActor* OverlappedActor, AActor* OtherActor);

	UFUNCTION()
	void OnActorEndOverlapCallback(AActor* OverlappedActor, AActor* OtherActor);
		
};

// cpp
// Fill out your copyright notice in the Description page of Project Settings.


#include "ActorComponent/CSCustomGravityDirComponent.h"
#include "Net/UnrealNetwork.h"
#include "GameFramework/Character.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "Actor/CSGravityCore.h"
#include "ChronoSpace.h"

UCSCustomGravityDirComponent::UCSCustomGravityDirComponent()
{
	PrimaryComponentTick.bCanEverTick = true;
	SetIsReplicatedByDefault(true);
	OrgGravityDirection = FVector(0.0f, 0.0f, -1.0f);
}


// Called when the game starts
void UCSCustomGravityDirComponent::BeginPlay()
{
	Super::BeginPlay();

	if ( GetOwner() )
	{
		OwnerCharacter = Cast<ACharacter>(GetOwner());

		if ( OwnerCharacter && OwnerCharacter->HasAuthority() )
		{
			UE_LOG(LogCS, Log, TEXT("[NetMode : %d] BeginPlay"), GetNetMode());
			OwnerCharacter->OnActorBeginOverlap.AddDynamic(this, &UCSCustomGravityDirComponent::OnActorBeginOverlapCallback);
			OwnerCharacter->OnActorEndOverlap.AddDynamic(this, &UCSCustomGravityDirComponent::OnActorEndOverlapCallback);
		}
	}

	
}

void UCSCustomGravityDirComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(UCSCustomGravityDirComponent, CurrentGravityDirection);
}

FVector UCSCustomGravityDirComponent::GetDirection()
{
	if ( CurrentGravityCore )
	{
		FVector CoreLocation = CurrentGravityCore->GetActorLocation();
		FVector CharacterLocation = OwnerCharacter->GetActorLocation();

		return (CoreLocation - CharacterLocation).GetSafeNormal();
	}

	return FVector();
} 

void UCSCustomGravityDirComponent::OnRep_CurrentGravityDirection()
{
	//UE_LOG(LogCS, Log, TEXT("[NetMode : %d] OnRep_CurrentGravityDirection, (%f, %f, %f)"), GetNetMode(), CurrentGravityDirection.X, CurrentGravityDirection.Y, CurrentGravityDirection.Z);
	if (OwnerCharacter)
	{
		OwnerCharacter->GetCharacterMovement()->SetGravityDirection(CurrentGravityDirection);
	}
}

// Called every frame
void UCSCustomGravityDirComponent::TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);

	if (OwnerCharacter == nullptr) return;

	if ( OwnerCharacter->HasAuthority() && CurrentGravityCore )
	{
		CurrentGravityDirection = GetDirection();
		OwnerCharacter->GetCharacterMovement()->SetGravityDirection(CurrentGravityDirection);
	}
}

void UCSCustomGravityDirComponent::OnActorBeginOverlapCallback(AActor* OverlappedActor, AActor* OtherActor)
{
	ACSGravityCore* Core = Cast<ACSGravityCore>(OtherActor);

	if ( Core )
	{
		CurrentGravityCore = Core;
	}
}

void UCSCustomGravityDirComponent::OnActorEndOverlapCallback(AActor* OverlappedActor, AActor* OtherActor)
{
	ACSGravityCore* Core = Cast<ACSGravityCore>(OtherActor);
	ACharacter* Character = Cast<ACharacter>(GetOwner());

	if (Core)
	{
		CurrentGravityDirection = OrgGravityDirection;
		Character->GetCharacterMovement()->SetGravityDirection(OrgGravityDirection);
		CurrentGravityCore = nullptr;
	}
}

액터 컴포넌트에서 GravityDirection 리플리케이션 하고, OnRep_ 만들어서 연결