728x90
반응형
- 정리
- 캐릭터 공격 구현
- 네트웍 멀티플레이어 구현의 기본 원칙의 이해
- 네트웍 멀티플레이어에서 동작하는 캐릭터 공격 구현
- 액터 컴포넌트 리플리케이션의 설정과 관련 이벤트 함수의 학습
- 캐릭터의 체력 프로퍼티 동기화 구현
- 캐릭터 공격 구현
- 기존의 콤보어택으로 구성되어 있던 공격방식을 단일공격방식으로 변경해준다.
- 네트웍 멀티플레이의 구현을 위한 4원칙
- 클라이언트의 명령은 Server RPC를 사용한다.
- 중요한 게임 플레이 판정은 서버에서 처리한다.
- 게임 플레이에 영향을 주는 중요한 정보는 프로퍼티 리플리케이션을 사용한다.
- 클라이언트의 시각적인 효과(Cosmetic)는 Client RPC와 Multicast RPC를 사용한다. (중요하지 않은 정보)
- 네트웍 멀티플레이를 위한 공격 기능 구현 기획
- 입력 명령을 전달한 이후에는 모두 서버에서 처리하도록 설계
- 액터 컴포넌트 리플리케이션
- 언리얼에서 리플리케이션의 주체는 액터임
- 액터가 소유하는 언리얼 오브젝트에 대해 리플리케이션 진행이 가능
- 이를 통틀어 서브오브젝트(Subobject)라고도 함
- 스탯을 관리하는 액터 컴포넌트의 리플리케이션 설정
- 리플리케이션을 지정 : SetIsReplicated(true)
- 리플리케이션이 준비되면 호출되는 이벤트 함수 : ReadyForReplication
protected:
virtual void GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const override;
void Attack();
virtual void AttackHitCheck() override;
UFUNCTION(Server, Reliable, WithValidation)
void ServerRPCAttack();
UFUNCTION(NetMulticast, Reliable)
void MulticastRPCAttack();
// 프로퍼티로 승격
UPROPERTY(ReplicatedUsing = OnRep_CanAttack)
uint8 bCanAttack : 1;
UFUNCTION()
void OnRep_CanAttack();
- ABCharacterPlayer.h
// 캐릭터가 가지고 있는 프로퍼티들을 모두 상속받아 구현해줘야 한다.
void AABCharacterPlayer::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(AABCharacterPlayer, bCanAttack);
}
void AABCharacterPlayer::Attack()
{
//ProcessComboCommand();
if (bCanAttack)
{
// 공격을 하게 되면 Server로부터 보내도록
// 신호를 받은 서버는 ServerRPCAttack_Implementation를 실행하게 된다.
ServerRPCAttack();
}
}
void AABCharacterPlayer::AttackHitCheck()
{
// 서버에서만 동작하도록
if (HasAuthority())
{
AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));
FHitResult OutHitResult;
FCollisionQueryParams Params(SCENE_QUERY_STAT(Attack), false, this);
const float AttackRange = Stat->GetTotalStat().AttackRange;
const float AttackRadius = Stat->GetAttackRadius();
const float AttackDamage = Stat->GetTotalStat().Attack;
const FVector Start = GetActorLocation() + GetActorForwardVector() * GetCapsuleComponent()->GetScaledCapsuleRadius();
const FVector End = Start + GetActorForwardVector() * AttackRange;
bool HitDetected = GetWorld()->SweepSingleByChannel(OutHitResult, Start, End, FQuat::Identity, CCHANNEL_ABACTION, FCollisionShape::MakeSphere(AttackRadius), Params);
if (HitDetected)
{
FDamageEvent DamageEvent;
OutHitResult.GetActor()->TakeDamage(AttackDamage, DamageEvent, GetController(), this);
}
#if ENABLE_DRAW_DEBUG
FVector CapsuleOrigin = Start + (End - Start) * 0.5f;
float CapsuleHalfHeight = AttackRange * 0.5f;
FColor DrawColor = HitDetected ? FColor::Green : FColor::Red;
DrawDebugCapsule(GetWorld(), CapsuleOrigin, CapsuleHalfHeight, AttackRadius, FRotationMatrix::MakeFromZ(GetActorForwardVector()).ToQuat(), DrawColor, false, 5.0f);
#endif
}
}
bool AABCharacterPlayer::ServerRPCAttack_Validate()
{
return true;
}
// 신호를 받은 서버는 ServerRPCAttack_Implementation를 실행하게 된다.
void AABCharacterPlayer::ServerRPCAttack_Implementation()
{
AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));
// 서버는 서버와 모든 클라이언트에게 명령을 보내도록
MulticastRPCAttack();
}
// Multicast RPC
// 서버는 로컬로 실행이 되기 때문에 바로 호출, 다른 모든 클라이언트는 네트워크를 통해서 패킷이 전송되면 함수가 호출
void AABCharacterPlayer::MulticastRPCAttack_Implementation()
{
AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));
// 서버에서 실행
// HasAuthority를 통해 서버로직으로 분리, 서버에서는 중요한 변수들을 설정하도록
if (HasAuthority())
{
// bCanAttack을 변수에서 프로퍼티로 승격시켜 서버에서 해당 값을 바꿀 때 자동으로 모든 클라이언트에 바뀐 값이 전송되도록
bCanAttack = false;
// OnRep 이벤트 함수의 경우 서버에서는 실행되지 않고, 클라이언트에서만 실행됨.
// 그렇기 때문에 서버에서는 자동으로 호출되지않기 때문에 명시적으로 호출해줘야 함
OnRep_CanAttack();
FTimerHandle Handle;
GetWorld()->GetTimerManager().SetTimer(Handle, FTimerDelegate::CreateLambda([&]
{
bCanAttack = true;
OnRep_CanAttack();
}
), AttackTime, false, -1.0f);
}
// 서버와 클라이언트 모두 같이 재생
UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance();
AnimInstance->Montage_Play(ComboActionMontage);
}
// bCanAttack이 프로퍼티이니, 해당 값이 바뀌면 움직임이 바뀔 수 있도록
void AABCharacterPlayer::OnRep_CanAttack()
{
// 공격할 수 없다면 움직임 x
if (!bCanAttack)
{
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None);
}
// 공격할 수 있다면 움직임 ok
else
{
GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking);
}
}
- ABCharacterPlayer.cpp
- bCanAttack이라는 공격에 대한 중요한 변수를 프로퍼티로 승격시켜주고, 해당 값이 변경될 때마다 OnRep_CanAttack함수가 호출되도록 설정해준다.
- Attack 함수에서 공격을 하게 되면 Server로부터 보내도록 신호를 받은 서버는 ServerRPCAttack_Implementation를 실행하게 된다. 신호를 받은 서버는 서버와 모든 클라이언트에게 명령을 보내도록 MulticastRPCAttack를 호출해 모두에게 명령을 보낸다.
- 또한, 해당 코드는 서버와 클라이언트 둘 다 실행이 되기 때문에 서버에서만 호출될 수 있는 로직을 분리해주기 위해 HasAuthority를 통해 분리시켜준다.
protected:
void SetHp(float NewHp);
// 프로퍼티로 승격. 프로퍼티가 변경될 때마다 OnRep_CurrentHp함수가 호출되도록
UPROPERTY(ReplicatedUsing = OnRep_CurrentHp, Transient, VisibleInstanceOnly, Category = Stat)
float CurrentHp;
// ...
protected:
virtual void BeginPlay() override;
virtual void ReadyForReplication() override;
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;
UFUNCTION()
void OnRep_CurrentHp();
- ABCharacterStatComponent.h
UABCharacterStatComponent::UABCharacterStatComponent()
{
CurrentLevel = 1;
AttackRadius = 50.0f;
bWantsInitializeComponent = true;
SetIsReplicated(true);
}
void UABCharacterStatComponent::BeginPlay()
{
Super::BeginPlay();
AB_SUBLOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));
}
void UABCharacterStatComponent::ReadyForReplication()
{
AB_SUBLOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin"));
Super::ReadyForReplication();
}
void UABCharacterStatComponent::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// Replication 매크로
DOREPLIFETIME(UABCharacterStatComponent, CurrentHp);
}
// 리플리케이션
void UABCharacterStatComponent::OnRep_CurrentHp()
{
OnHpChanged.Broadcast(CurrentHp);
if (CurrentHp <= KINDA_SMALL_NUMBER)
{
OnHpZero.Broadcast();
}
}
- ABCharacterStatComponent.cpp
- 생성자에 SetIsReplicated를 true로 설정해준다. 그래야 해당 액터 컴포넌트는 네트워크로 리플리케이션될 준비가 되어 있다고 볼 수 있다.
- GetLifetimeReplicatedProps를 통해 프로퍼티를 등록해준다.
- 리플리케이션이 되면 호출할 OnRep 함수를 구성해주고, 값이 변경될 때 브로드캐스트 되도록 설정해준다.
- 로그를 확인해보면 BeginPlay가 호출되기전에 ReadyForReplication이 호출되는 것을 확인할 수 있다.
- ReadyForReplication
- BeginPlay 이전에 호출이 되며, 이때 네트워크로 통신할 모든 준비를 마쳤다라고 이해하면 된다.
해당 포스트는 인프런의 <이득우의 언리얼 프로그래밍 Part3 - 네트웍 멀티플레이어 프레임웍의 이해>
강의를 수강하고 정리한 내용입니다.
이득우의 언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해 강의 | 이득우 - 인프런
이득우 | 또 하나의 언리얼 엔진이라고도 불리는 네트웍 멀티플레이어 프레임웍을 학습합니다. 네트웍 멀티플레이어 게임을 제작할 때 반드시 알아야 하는 주요 개념, 내부 동작 원리, 최적화
www.inflearn.com
728x90
'공부 > 이득우의 언리얼 프로그래밍' 카테고리의 다른 글
[Study] Part 3 - 움직임 리플리케이션 (11/15) (3) | 2024.08.28 |
---|---|
[Study] Part 3 - 캐릭터 공격 구현 개선 (10/15) (0) | 2024.08.27 |
[Study] Part 3 - RPC 기초 (8/15) (0) | 2024.08.23 |
[Study] Part 3 - 액터 리플리케이션 로우레벨 플로우 (7/15) (0) | 2024.08.21 |
[Study] Part 3 - 액터 리플리케이션 빈도와 연관성 (6/15) (0) | 2024.08.20 |