void ADCPlayerController::BeginPlay()
{
Super::BeginPlay();
if (IsLocalController()) // 로컬 플레이어만
{
// UI 표시, 카메라 설정
}
}
DCNPCController.cpp/.h, DCNPC.cpp/.h, 캐릭터 이동 및 입력 관련)IsLocallyControlled() 체크OnRep 함수에서 IsLocallyControlled() 체크HasAuthority())에서만 실행void ADCCharacter::BeginPlay()
{
Super::BeginPlay();
if (IsLocallyControlled()) // 내가 조종하는 캐릭터
{
// 사운드 재생, 카메라 효과
}
}
→ 근데 이건 수정 안 함
HasAuthority() 또는 IsRunningDedicatedServer() 를 사용해 서버 권한을 체크한 후 실행하기IsLocallyControlled() 또는 GetNetMode() == NM_Client 조건으로 분기해서 클라이언트에서만 실행되게 함GameMode는 서버에만 존재하니까 클라이언트 전용 로직은 없음!
PostLogin() - 플레이어 입장 처리, AlivePlayerCount 증가Logout() - 플레이어 퇴장 처리, AlivePlayerCount 감소BeginPlay() - 게임 상태 초기화, 타이머 시작HandlePlayerDeath() - 플레이어 사망 처리, 랭킹 부여HandleGameOver() - 게임 종료 처리, ServerTravel 실행TriggerNightEvent() - NPC 춤 이벤트 관리OnEnemyKilled() - 킬 카운트 증가각 함수 시작 부분의 if (!HasAuthority()) return; 은 방어코드일 뿐
GameMode는 이미 서버 전용이므로 항상 true
클라이언트에서 실행되는 부분은 다른 클래스의 Client RPC임
// HandleGameOver()에서 호출
MyPC->Client_ShowGameOverUI(GameOverWidgetClass);
// ShowHiddenVictoryUI()에서 호출
MyPC->Client_ShowHiddenVictoryUI(HiddenVictoryWidgetClass, WinnerNickname);
// QuitGameAfterHiddenVictory()에서 호출
MyPC->Client_QuitGame();
[CH4-18] Unreal Engine 배경음악 겹침 문제 해결
Play Sound2D 가 아니라 GameInstance 중앙 관리 방식으로 Play Background Music 함수 호출했음if (UDCGameInstance* GI = GetGameInstance<UDCGameInstance>())
{
GI->PlayBackgroundMusic(MainLevelMusic); // GameInstance만 재생
}
GetGameInstance() 는 현재 실행 중인 머신의 GameInstance를 반환📎[이득우 네트워크 멀티플레이] 08. RPC 기초
📎RPC 이해하기
📎[UE Net] RPC (Remote Procedure Call)
Ownership (소유권)
AActor::GetNetConnection() 을 통해 소유권 확인 가능Reliable vs Unreliable
UFUNCTION(Server, Reliable)
void Server_IncrementDanceCount();
// 보안 검증 추가 선언
UFUNCTION(Server, Reliable, WithValidation)
void Server_IncrementDanceCount();
Server, Reliable - 클라이언트에서 서버로 호출Server_IncrementDanceCount - Server_ 접두사 관례 사용void ADCCharacter::Server_IncrementDanceCount_Implementation()
{
// Server RPC - 서버에서 실행
DanceCount++;
UE_LOG(LogTemp, Warning, TEXT("[Server] DanceCount: %d"), DanceCount);
if (DanceCount >= 15)
{
HandleHiddenWinCondition();
}
}
// 검증 함수 (WithValidation 사용 시 필수)
bool ADCCharacter::Server_IncrementDanceCount_Validate()
{
// 악의적인 요청 검증
// false 반환 시 해당 클라이언트는 강제 접속 해제됨
return true; // 검증 통과
}
_Implementation 접미사가 필수: 실제 로직이 실행되는 함수_Validate 함수:Server_IncrementDanceCount() 호출_Validate() 함수 먼저 실행_Implementation() 함수가 서버에서만 실행됨// ADCCharacter.cpp - 클라이언트에서 실행
void ADCCharacter::OnDanceInput()
{
// 서버에 춤 카운트 증가 요청
Server_IncrementDanceCount();
}
이 함수는 클라이언트에서 호출되지만 실제 실행은 서버에서만 이루어짐
UFUNCTION(Client, Reliable)
void Client_OverlapEffect(class UNiagaraSystem* Effect, float DurationSeconds);
Client, Reliable - 서버에서 특정 클라이언트로 호출Client_OverlapEffect - Client_접두사 관례 사용void ADCCharacter::Client_OverlapEffect_Implementation(UNiagaraSystem* Effect, float DurationSeconds)
{
// Client RPC - 클라이언트에서 실행
if (!Effect) return; // 유효성 검사: 전달받은 나이아가라 이펙트가 null이면 함수 즉시 종료
// 나이아가라 이펙트 생성 및 부착
UNiagaraComponent* EffectComp = UNiagaraFunctionLibrary::SpawnSystemAttached(
Effect, // 재생할 나이아가라 시스템
GetMesh(), // 캐릭터 메시에 부착
NAME_None, // 특정 소켓 이름 : 없음
FVector::ZeroVector, // 상대 위치 (0, 0, 0)
FRotator::ZeroRotator, // 상대 회전 (0, 0, 0)
EAttachLocation::KeepRelativeOffset, // 상대 오프셋 유지
true // 자동 활성화
);
if (EffectComp)
{
// 자동 제거 타이머 설정
FTimerHandle EffectTimer;
GetWorldTimerManager().SetTimer(
EffectTimer,
[EffectComp]() // 람다 함수로 EffectComp 캡처
{
if (IsValid(EffectComp))
{
EffectComp->DestroyComponent(); // 컴포넌트 삭제
}
},
DurationSeconds, // 서버에서 전달받은 지속시간
false // 반복 안 함 (한 번만 실행)
);
}
}
_Implementation 접미사가 필수Client_OverlapEffect() 실행_Implementation() 함수를 실행하지 않음_Implementation() 함수 실행SpawnSystemAttached() 는 나이아가라 이펙트를 캐릭터 메시에 부착해 생성 → 캐릭터가 움직여도 이펙트가 따라다님// 서로 다른 2개의 Client RPC 호출함
void ABaseItem::SendOverlapFeedbackTo(class ADCCharacter* Target)
{
// 대상 검증에 실패하면 아무 것도 하지 않음
if (!Target) return;
if (ADCPlayerController* PC = Cast<ADCPlayerController>(Target->GetController()))
{
if (USoundBase* S = ResolveGlobalOverlapSound())
{
// 플레이어 컨트롤러에서 사운드를 클라이언트 재생해요
// [1번] PlayerController의 Client RPC
PC->ClientPlaySound(S);
}
}
if (OverlapEffect)
{
// 지정 시간만큼 이펙트를 재생해요
// [2번] Character의 Client RPC
Target->Client_OverlapEffect(OverlapEffect, OverlapEffectPlaySeconds);
}
}
[1번] PlayerController의 Client RPC 호출
ClientPlayerSound() 는 PlayerController에 정의된 Client RPC[2번] Character의 Client RPC 호출
Client_OverlapEffetct() 는 ADCCharacter에 정의된 Client RPC 실행 흐름
1. BaseItem에서 Overlap 감지 (서버)
2. 네트워크 전송
- PC->ClientPlaySound() → 해당 클라이언트의 PlayerController로 전송
- Target->Client_OverlapEffect() → 해당 클라이언트의 Character로 전송
3. 클라이언트에서 실행
UFUNCTION(NetMulticast, Unreliable)
void Multicast_PlayEffect();
NetMulticast, Unreliable - 서버에서 모든 클라이언트로 브로드캐스트Multicast_PlayEffect - Multicast_ 접두사 관례 사용void AMyActor::Multicast_PlayEffect_Implementation()
{
// NetMulticast RPC - 서버 + 모든 클라이언트에서 실행
if (ExplosionEffect)
{
UNiagaraFunctionLibrary::SpawnSystemAtLocation(
GetWorld(),
ExplosionEffect,
GetActorLocation(),
GetActorRotation()
);
}
if (ExplosionSound)
{
UGameplayStatics::PlaySoundAtLocation(
GetWorld(),
ExplosionSound,
GetActorLocation()
);
}
UE_LOG(LogTemp, Warning, TEXT("Effect played on %s"),
HasAuthority() ? TEXT("Server") : TEXT("Client"));
}
_Implementation 접미사가 필수 서버에서 호출한 경우
1. 서버가 호출: Multicast_PlayEffect() 실행
2. 네트워크 전송: 모든 클라이언트에게 RPC 브로드캐스트
3. 서버에서 실행: 서버 자신도 _Implementation() 함수 실행
4, 모든 클라이언트에서 실행: 연결된 모든 클라이언트에서 _Implementation() 함수 실행
5. 결과: 모든 플레이어가 동일한 이펙트/사운드를 보고 듣게 됨
클라이언트에서 호출한 경우 (잘못된 사용)
1. 클라이언트가 호출: Multicast_PlayEffect() 실행
2. 로컬에서만 실행: 해당 클라이언트에서만 실행됨
3. 네트워크 전송 안됨: 서버나 다른 클라이언트에 전달되지 않음
4. 결과: 호출한 클라이언트만 이펙트를 보게 되어 동기화 실패
올바른 사용 - 서버에서만 호출
void AMyGrenade::Explode()
{
if (HasAuthority()) // 서버 체크
{
// 폭발 데미지 처리 (서버 전용 로직)
ApplyRadialDamage();
// 모든 플레이어에게 폭발 이펙트 표시
Multicast_PlayExplosionEffect();
}
}
// 실제 게임 예시
// 서버에서 공격 검증 후 모든 플레이어에게 애니메이션 동기화
void AMyCharacter::Attack()
{
if (HasAuthority())
{
// 서버: 데미지 처리
ApplyDamageToTarget();
// 모든 플레이어: 공격 애니메이션 재생
Multicast_PlayAttackAnimation();
}
}
void AMyCharacter::Multicast_PlayAttackAnimation_Implementation()
{
// 서버 + 모든 클라이언트에서 몽타주 재생
if (AttackMontage)
{
PlayAnimMontage(AttackMontage);
}
}
Property Replication과 차이점
| 비교 | Property Replication | NetMulticast RPC |
|---|---|---|
| 동작 방식 | 값 변경 시 자동 동기화, OnRep 콜백 실행 | 서버가 명시적으로 호출할 때만 실행 |
| 데이터 유지 | 서버에 값이 저장되어 지속됨 | 호출 시점에만 실행, 데이터는 유지되지 않음 |
| 새 플레이어 접속 | 자동으로 현재 값이 동기화됨 | 이전 호출은 재실행되지 않음 |
| 사용 목적 | 게임 상태, 체력, 점수 등 지속 데이터 | 일회성 이펙트, 사운드, 애니메이션 |
| 네트워크 부하 | 값이 변경될 때만 전송 | 호출할 때마다 전송 |
| 비교 | Server RPC | Client RPC | NetMulticast RPC |
|---|---|---|---|
| 호출 위치 | 클라이언트에서 호출 | 서버에서 호출 | 서버에서 호출 (권장) |
| 실행 위치 | 서버에서만 실행 | 해당 클라이언트에서만 실행 | 서버와 모든 클라이언트에서 실행 |
| 호출 방향 | Client → Server | Server → 특정 Client | Server → All Clients (+ Server) |
| Ownership 필요 | 필수 | 필수 | 불필요 (연관성 기반) |
| 서버에서 실행 | 실행됨 | 실행 안됨 | 실행됨 |
| 네트워크 전송 | 1개 서버로 전송 | 1개 클라이언트로 전송 | 모든 클라이언트에게 브로드캐스트 |
| 주요 사용 목적 | 클라이언트 액션을 서버로 전달 | 특정 플레이어 전용 효과 | 모든 플레이어가 봐야 하는 효과 |
| 사용 예시 | 공격 요청, 아이템 습득, 문 열기 | UI 업데이트, 개인 알림, 카메라 효과 | 폭발 이펙트, 총소리, 애니메이션 동기화 |
| Validation 지원 | WithValidation 사용 가능 | 지원 안함 | 지원 안함 |
| 보안 중요도 | 높음 (클라이언트 검증 필요) | 낮음 (서버가 호출) | 낮음 (서버가 호출) |
| 네트워크 부하 | 낮음 (1:1 전송) | 낮음 (1:1 전송) | 높음 (1:N 브로드캐스트) |
| 권장 Reliability | Reliable | Unreliable (이펙트용) | Unreliable (이펙트용) |
| 클라이언트에서 호출 시 | 서버로 전송되어 실행됨 | 작동 안함 | 로컬에서만 실행 (동기화 안됨) |