네트워크 데이터 관련 용어
클라 액터 BeginPlay flow
OnPossess
NetLocalOnClient
OnRep함수 vs RepNotify 함수
NetUpdateFrequency
모든 멀티플레이 관련 데이터들이 드나드는 통로
서버는 접속한 클라개수만큼 ClientConnection가지고, 클라는 하나의 ServerConnection만 가짐
NetConnection 클래스는 Channels라는 멤버변수를 가짐
현재 연결된 네트워크에서 열려있는 모든 네트워크 채널(UChannel*)들을 배열(TArray<UChannel*>) 형태로 관리
Channels를 통해 액터 복제, RPC, 네트워크 동기화 등 네트워크 작업울 험
채널에는 ActorChannel, ControlChannel, VoiceChannel 등이 있으며 이 중에 ActorChannel이 반드시 열려있어야 Actor Replication이 가능해짐
PlayerController 액터의 네트워크 채널이 열려 초기화가 이루어지면, 클라이언트 쪽에서 APlayerController::OnActorChannelOpen() 함수가 호출됨. 이 이후에야 Replication이 가능

NetConnection 안에 Channel들이 관리되고 있고, 이 채널들 통해 Packet이 왔다갔다 하는데, 패킷은 번치로 이루어짐
번치로는 RPC, 동기화될 속성값 이런것들
StartPlay가 호출되면 AGameStateBase::HandleBeginPlay()가 호출됨void AGameModeBase::StartPlay()
{
GameState->HandleBeginPlay();
}
void AGameStateBase::HandleBeginPlay()
{
bReplicatedHasBegunPlay = true;
GetWorldSettings()->NotifyBeginPlay(); // 서버의 모든 액터도 BeginPlay 실행
GetWorldSettings()->NotifyMatchStarted();
}
// GameStateBase.h
class AGameStateBase : public AInfo
{
UPROPERTY(Transient, ReplicatedUsing = OnRep_ReplicatedHasBegunPlay)
bool bReplicatedHasBegunPlay;
virtual void OnRep_ReplicatedHasBegunPlay();
}
AGameStateBase::HandleBeginPlay()함수는 게임 스테이트에 있는 bReplicatedHasBegunPlay변수를 true로 설정하게 되고, 값이 변경됨에 따라 클라로 동기화 됨
클라로 동기화되면서 AGameStateBase::OnRep_ReplicatedHasBegunPlay()가 클라에서만 호출되게 되고, 이 함수 역시 BeginPlay()를 알리며 다른 액터들의 BeginPlay가 시작됨
void AGameStateBase::OnRep_ReplicatedHasBegunPlay()
{
if (bReplicatedHasBegunPlay && GetLocalRole() != ROLE_Authority)
{
GetWorldSettings()->NotifyBeginPlay(); // 액터들 BeginPlay시작
GetWorldSettings()->NotifyMatchStarted();
}
}
void AWorldSettings::NotifyBeginPlay()
{
if (!World->bBegunPlay) {
for (FActorIterator It(World); It; ++It) { // 액터 순회하면서
// ... //
It->DispatchBeginPlay(bFromLevelLoad); // 함수 호출
// ... //
}
void AActor::DispatchBeginPlay(bool bFromLevelStreaming)
{
if (World) {
BeginPlay(); // BeginPlay 시작
// ... //
}
StartPlay는 게임을 시작하라는 지시 함수이고, 이 함수통해서 다른 모든 액터들의 BeginPlay가 호출되게 된다컨트롤러가 폰을 소유(Possess)할 때, 폰의 오너로 컨트롤러가 등록되면서 NetConnection 패밀리에 들어온다고 공부하였음
APlayerController::OnPossess()에서 해당 폰의 PossessedBy()를 호출해 컨트롤러 자신을 오너로 등록
void AController::OnPossess(APawn* InPawn)
{
// ... //
InPawn->PossessedBy(this);
SetPawn(InPawn);
}
void APawn::PossessedBy(AController* NewController)
{
SetOwner(NewController);
AController* const OldController = Controller;
Controller = NewController;
ForceNetUpdate(); // 네트워크 업데이트
}
그리고 이렇게 오너가 바뀌면, 클라이언트의 폰에서 OnRep_Owner()가 호출됨
오너로 등록되면 Simulated Proxy에서 Autonomous Proxy로 바뀐다
소유 해제할 때는 OnUnPossess호출함
void APlayerController::OnUnPossess()
{
if (GetPawn() != NULL)
{
// ... //
GetPawn()->UnPossessed();
}
SetPawn(NULL);
}
오너 설정 과정은 다 서버에서만 일어남
OnPossess는 서버에서만 호출, PossessedBy는 서버/클라 둘 다 호출됨
언리얼 에디터에서 레벨에 직접 배치하여 무조건 스폰되는 액터들은 NetLoadOnClient 속성을 true로 지정하여 클라가 스스로 스폰하게 함
이 속성값을 거짓으로하면 서버에서 생성한 후 액터를 클라로 복제해줌


따라서 이 속성값이 참인 액터들은 각 클라가 레벨스트리밍하면서 직접 소환하게 됨
알고보니 이 속성 말고도 bReplicates, Replicate Movement 등 레플리케이션 관련 속성들이 더 있어서, 이것만 킨다고 동기화 끊기진 않음
특히 bReplicates은 우리가 true로 했기에 서버랑 동기화 됨
전자는 이전에 공부했던 C++ 함수. 즉, 서버에서 멤버변수를 수정하면 클라이언트로 동기화되고, 이 복제됨에 따라 클라에서만 해당 함수가 호출됨
후자는 블루프린트 함수. 얘는 서버와 클라 둘 다 호출됨.
C++에선 복제될 때뿐만이 아니라 그냥 호출하고 싶으면 명시적으로 호출도 가능했음.
하지만, 블루프린트 함수는 명시적으로 따로 호출은 못 함. 조건 맞으면 알아서 호출되는 함수
이전 프로젝트에서 사용해봤듯이, 각 객체들은 서버에서 클라로의 복제빈도수 최대치가 정해져있음
이 속성은 NetUpdateFrequency로 1초에 최대 몇 번 동기화할건지 정함
물론 서버의 성능에 따라 최대치를 만족 못 시킬 수도 있다
SetNetUpdateFrequency(1); // 1초에 1번만 레플리케이션 시도
NetUpdatePeriod = 1 / GetNetUpdateFrequency(); // Getter로 값 받기
void ADXBox::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
if (HasAuthority()) // 서버에선 틱마다 박스 회전
{
AddActorLocalRotation(FRotator(0.f, RotationSpeed * DeltaSeconds, 0.f));
ServerRotationYaw = RootComponent->GetComponentRotation().Yaw;
}
else // 클라에선 동기화 받은 값으로 다음 동기화값을 예측해 보간
{
DeltaSecFromRep += DeltaSeconds;
float LerpR = FMath::Clamp(DeltaSecFromRep / NetUpdatePeriod, 0.f, 1.f);
// 다음에 동기화 되었을 때의 Rotation 예측
float NextRotationYaw = ServerRotationYaw + RotationSpeed * NetUpdatePeriod;
// 다음 동기화 회전값으로 틱당 회전
float NextTickRotationYaw = FMath::Lerp(ServerRotationYaw,
NextRotationYaw,
LerpR);
SetActorRotation(FRotator(0.f, NextTickRotationYaw, 0.f));
}
}
// 1초마다 ServerRotationYaw이 동기화되면 불리는 함수
void ADXBox::OnRep_ServerRotationYaw()
{
SetActorRotation(FRotator(0.f, ServerRotationYaw, 0.f));
AccDeltaSecondSinceReplicated = 0.f;
}
AddActorWorldRotation같은 함수로 틱당 더해주는게 더 쉬운 코드인데 Set이용해서 해봄