[UE Net] 캐릭터 공격 구현 개선
- 느린 통신 환경에도 대응되는 캐릭터의 공격 구현의 개선
- 네트웍 최적화를 위한 다양한 고려 사항의 이해
인프런 이득우님의 '언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해' 강의를 참고하였습니다.
😎 [이득우의 언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해] 강의 들으러 가기!
목차
캐릭터 공격 구현의 개선
목표
의도적으로 패킷 랙을 유발시켜서 기존에 구현한 공격 기능의 문제점을 살펴보자.
캐릭터의 공격 구현의 개선하자.

캐릭터의 공격 구현의 문제점
- 클라이언트의 모든 행동은 서버를 거친 후에 수행되도록 설계되어 있음
- 통신 부하가 발생하는 경우 사용자 경험이 나빠짐.
- 만약에 통신 랙이 심한 경우에는 사용자가 공격 명령을 입력해도 애니메이션이 늦게 재생되거나 시각적으로 보여지는 타이밍에서의 판정이 클라이언트와 서버가 서로 일치하지 않는 경우가 발생할 수 있다.
- 의도적으로 패킷 랙을 발생시킨 후 이의 결과를 확인.
DefaultEngine.ini

학습을 위해 DefaultEngine.ini에 위의 키워드를 작성하여 의도적으로 패킷 통신 랙을 유발시킨다.
이렇게 지정하게 되면 에디터에서는 0.5초(=500ms)의 딜레이를 가지고 패킷을 전송한다.
기존에 구현한 공격 기능의 리뷰
랙이 발생하는 경우, 클라이언트의 반응이 많이 느려지는 문제가 발생
- 패킷 랙이 발생하면 입력이 서버로 전달되는 과정에서 시간이 오래 소요됨.
- 서버에서 클라이언트로 다시 전달되는 과정에서 시간이 중복으로 소요됨. 이 중복으로 소요되는 기간 동안에 입력을 넣었지만 애니메이션이 재생하지 않고 대기하고 있는 상황이 발생하게 된다.
- 공력을 입력했는데 서버로 응답이 올 때까지 대기하고 있어야 하는 상황이 발생한다.

서버에서 모든 것을 처리하는 방식이 데이터 관리 측면에서는 안전한 방법일 수도 있지만 통신 상태가 나쁜 상황에서 사용자는 한 박자 늦은 플레이를 진행할 수밖에 없어서 공정한 플레이를 진행하기가 어렵고 정확한 판정을 내기가 어렵다.
캐릭터 공격 구현의 개선
[기본 원칙]
- 클라이언트의 명령은 Server RPC를 사용한다.
- 중요한 게임 플레이 판정은 서버에서 처리한다.
- 게임 플레이에 영향을 주는 중요한 정보는 프로퍼티 리플리케이션을 사용한다.
- 클라이언트의 시각적인 효과(Cosmetic)는 Client RPC와 Multicast RPC를 사용한다.
[개선점]
- 클라이언트에서 처리할 수 있는 기능은 최대한 클라이언트에서 직접 처리하여 반응성을 높인다.
- 최종 판정은 서버에서 진행하되 다양한 로직을 활용해 자세하게 검증한다.
- 네트웍 데이터 전송을 최소화한다.
개선된 공격 기능의 설계
클라이언트의 반응성을 개선하고 서버에서는 클라이언트의 요청을 검증을 통해 구현

- 클라이언트에서 입력 명령 → 서버의 RPC 호출 / 애니메이션 재생 (동시에 진행)
- 애니메이션의 타이밍은 살짝 다르게 동작할 수 있다.
- 중요한 정보는 동기화 되도록 설정 할 것임 → 이를 위해 시간 정보를 활용한다.
- 클라이언트가 Server RPC를 보낼 때 현재 클라이언트의 시간 정보를 보냄
- 서버는 패킷을 받은 시간과 Server RPC를 보낸 시간을 비교해 얼마나 렉이 걸렸는지 측정
- 서버에서 판정을 하는 방법이 아닌 클라이언트에서 판정하는 방법으로 변경
- 서로 동일한 타이밍에 진행해야 공정하다.
- 클라이언트에서 판정을 확정하면 위조의 위험성이 있다. (그렇기 때문에 아래의 과정이 필요하다)
- 클라이언트의 판정 결과를 서버로 보내서 서버에서 판정 결과를 수용할지말지 판단하는 과정을 거친다.
- 서버가 판정 결과를 수용한다면 액터에게 데미지를 전달한다.
서버(=GameMode)의 시간을 가져오는 방법
GetWorld()->GetGameState()->GetServerWorldTimeSeconds();
※ 참고) 서버의 월드와 클라이언트의 월드는 엄연히 서로 다른 월드다.
클라이언트의 월드는 서버를 Simulate한 월드다.
우리는 클라이언트는 늦게 생성하기 때문에 클라이언트의 시간은 서버의 시간보다 늦게 흘러갈 수밖에 없다.
실습 예제
예제 코드 - 공격 및 피격 판정
ABCharacterPlayer.h
#pragma once #include "CoreMinimal.h" #include "Character/ABCharacterBase.h" #include "InputActionValue.h" #include "Interface/ABCharacterHUDInterface.h" #include "ABCharacterPlayer.generated.h" UCLASS() class ARENABATTLE_API AABCharacterPlayer : public AABCharacterBase, public IABCharacterHUDInterface { GENERATED_BODY() public: AABCharacterPlayer(); protected: virtual void BeginPlay() override; virtual void SetDead() override; virtual void PossessedBy(AController* NewController) override; virtual void OnRep_Owner() override; virtual void PostNetInit() override; public: virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; // Character Control Section protected: void ChangeCharacterControl(); void SetCharacterControl(ECharacterControlType NewCharacterControlType); virtual void SetCharacterControlData(const class UABCharacterControlData* CharacterControlData) override; // Camera Section protected: UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, Meta = (AllowPrivateAccess = "true")) TObjectPtr<class USpringArmComponent> CameraBoom; UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, Meta = (AllowPrivateAccess = "true")) TObjectPtr<class UCameraComponent> FollowCamera; // Input Section protected: UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true")) TObjectPtr<class UInputAction> JumpAction; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true")) TObjectPtr<class UInputAction> ChangeControlAction; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true")) TObjectPtr<class UInputAction> ShoulderMoveAction; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true")) TObjectPtr<class UInputAction> ShoulderLookAction; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true")) TObjectPtr<class UInputAction> QuaterMoveAction; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, Meta = (AllowPrivateAccess = "true")) TObjectPtr<class UInputAction> AttackAction; void ShoulderMove(const FInputActionValue& Value); void ShoulderLook(const FInputActionValue& Value); void QuaterMove(const FInputActionValue& Value); ECharacterControlType CurrentCharacterControlType; protected: virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override; void Attack(); void PlayAttackAnimation(); virtual void AttackHitCheck() override; void AttackHitConfirm(AActor* HitActor); void DrawDebugAttackRange(const FColor& DrawColor, FVector TraceStart, FVector TraceEnd, FVector Forward); UFUNCTION(Server, Reliable, WithValidation) void ServerRPCAttack(float AttackStartTime); UFUNCTION(NetMulticast, Unreliable) void MulticastRPCAttack(); UFUNCTION(Client, Unreliable) void ClientRPCPlayAnimation(AABCharacterPlayer* CharacterToPlay); UFUNCTION(Server, Reliable, WithValidation) void ServerRPCNotifyHit(const FHitResult& HitResult, float HitCheckTime); UFUNCTION(Server, Reliable, WithValidation) void ServerRPCNotifyMiss(FVector_NetQuantize TraceStart, FVector_NetQuantize TraceEnd, FVector_NetQuantizeNormal TraceDir, float HitCheckTime); UPROPERTY(ReplicatedUsing = OnRep_CanAttack) uint8 bCanAttack : 1; UFUNCTION() void OnRep_CanAttack(); float AttackTime = 1.4667f; float LastAttackStartTime = 0.0f; float AttackTimeDifference = 0.0f; float AcceptCheckDistance = 300.0f; float AcceptMinCheckTime = 0.15f; // UI Section protected: virtual void SetupHUDWidget(class UABHUDWidget* InHUDWidget) override; };
함수 추가
- void PlayAttackAnimation();
- UFUNCTION(Server, Reliable, WithValidation)
void ServerRPCNotifyHit(const FHitResult& HitResult, float HitChecktime); // 클라이언트에서 무언가 액터에 맞았을 때 서버로 판정을 보내는 함수 - UFUNCTION(Server, Reliable, WithValidation)
void ServerRPCNotifyMiss(FVector TraceStart, FVector TraceEnd, FVector TraceDir, float HitChecktime); - void AttackHitConfirm(AActor* HitActor); // 확인 후 데미지 전달 함수
함수 수정
- UFUNCTION(Server, Reliable, WithValidation)
void ServerRPCAttack(float AttackStartTime); - UFUNCTION(NetMulticast, Unreliable)
void MulticastRPCAttack(); - virtual void AttackHitCheck() override; // 공격 판정
변수 추가
- float LastAttackStartTime = 0.0f; // 마지막에 공격한 시간
- float AttackTimeDifference = 0.0f; // 클라이언트와 서버와의 시간 차이
- float AcceptMinCheckTime = 0.15f; // 최소한 이 시간 만큼은 지난 후에 판정을 진행
ABCharacterPlayer.cpp
#include "Character/ABCharacterPlayer.h" AABCharacterPlayer::AABCharacterPlayer() { // Camera ... // Input ... CurrentCharacterControlType = ECharacterControlType::Quater; bCanAttack = true; } void AABCharacterPlayer::BeginPlay() { Super::BeginPlay(); APlayerController* PlayerController = Cast<APlayerController>(GetController()); if (PlayerController) { EnableInput(PlayerController); } SetCharacterControl(CurrentCharacterControlType); } void AABCharacterPlayer::SetDead() { Super::SetDead(); APlayerController* PlayerController = Cast<APlayerController>(GetController()); if (PlayerController) { DisableInput(PlayerController); } } void AABCharacterPlayer::PossessedBy(AController* NewController) { AActor* OwnerActor = GetOwner(); Super::PossessedBy(NewController); OwnerActor = GetOwner(); } void AABCharacterPlayer::OnRep_Owner() { Super::OnRep_Owner(); AActor* OwnerActor = GetOwner(); } void AABCharacterPlayer::PostNetInit() { Super::PostNetInit(); } void AABCharacterPlayer::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) { Super::SetupPlayerInputComponent(PlayerInputComponent); UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent); EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump); //... } void AABCharacterPlayer::ChangeCharacterControl() { if (CurrentCharacterControlType == ECharacterControlType::Quater) { SetCharacterControl(ECharacterControlType::Shoulder); } else if (CurrentCharacterControlType == ECharacterControlType::Shoulder) { SetCharacterControl(ECharacterControlType::Quater); } } void AABCharacterPlayer::SetCharacterControl(ECharacterControlType NewCharacterControlType) { if (!IsLocallyControlled()) { return; } UABCharacterControlData* NewCharacterControl = CharacterControlManager[NewCharacterControlType]; check(NewCharacterControl); SetCharacterControlData(NewCharacterControl); APlayerController* PlayerController = CastChecked<APlayerController>(GetController()); if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer())) { Subsystem->ClearAllMappings(); UInputMappingContext* NewMappingContext = NewCharacterControl->InputMappingContext; if (NewMappingContext) { Subsystem->AddMappingContext(NewMappingContext, 0); } } CurrentCharacterControlType = NewCharacterControlType; } void AABCharacterPlayer::SetCharacterControlData(const UABCharacterControlData* CharacterControlData) { // ... } void AABCharacterPlayer::ShoulderMove(const FInputActionValue& Value) { ... } void AABCharacterPlayer::ShoulderLook(const FInputActionValue& Value) { ... } void AABCharacterPlayer::QuaterMove(const FInputActionValue& Value) { ... } void AABCharacterPlayer::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const { Super::GetLifetimeReplicatedProps(OutLifetimeProps); DOREPLIFETIME(AABCharacterPlayer, bCanAttack); } void AABCharacterPlayer::Attack() { //ProcessComboCommand(); if (bCanAttack) { if (!HasAuthority()) // 클라이언트 { bCanAttack = false; GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None); FTimerHandle Handle; GetWorld()->GetTimerManager().SetTimer(Handle, FTimerDelegate::CreateLambda([&] { bCanAttack = true; GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking); } ), AttackTime, false, -1.0f); PlayAttackAnimation(); // 클라이언트에서 공격 몽타주 재생 } ServerRPCAttack(GetWorld()->GetGameState()->GetServerWorldTimeSeconds()); } } void AABCharacterPlayer::PlayAttackAnimation() { UAnimInstance* AnimInstance = GetMesh()->GetAnimInstance(); AnimInstance->StopAllMontages(0.0f); AnimInstance->Montage_Play(ComboActionMontage); } void AABCharacterPlayer::AttackHitCheck() // 공격 판정 { if (IsLocallyControlled()) // 서버를 포함 플레이어에서 피격 체크를 한다(리슨 서버이니 서버도 플레이어가 있다) { 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 Forward = GetActorForwardVector(); const FVector Start = GetActorLocation() + Forward * GetCapsuleComponent()->GetScaledCapsuleRadius(); const FVector End = Start + GetActorForwardVector() * AttackRange; bool HitDetected = GetWorld()->SweepSingleByChannel(OutHitResult, Start, End, FQuat::Identity, CCHANNEL_ABACTION, FCollisionShape::MakeSphere(AttackRadius), Params); float HitCheckTime = GetWorld()->GetGameState()->GetServerWorldTimeSeconds(); // 서버 기준 피격시간 if (!HasAuthority()) // 클라이언트 { // 클라이언트의 경우, 서버쪽으로 RPC를 보내서 검증을 거쳐줘야 한다. if (HitDetected) // 맞았다면 { ServerRPCNotifyHit(OutHitResult, HitCheckTime); } else // 안 맞았다면 { ServerRPCNotifyMiss(Start, End, Forward, HitCheckTime); } } else // 서버 { // 리슨 서버의 서버쪽 플레이어는 검증 절차가 필요없다. FColor DebugColor = HitDetected ? FColor::Green : FColor::Red; DrawDebugAttackRange(DebugColor, Start, End, Forward); if (HitDetected) { AttackHitConfirm(OutHitResult.GetActor()); } } } } void AABCharacterPlayer::AttackHitConfirm(AActor* HitActor) // 데미지 전달 함수 { if (HasAuthority()) // 서버에서만 실행 { const float AttackDamage = Stat->GetTotalStat().Attack; FDamageEvent DamageEvent; HitActor->TakeDamage(AttackDamage, DamageEvent, GetController(), this); } } void AABCharacterPlayer::DrawDebugAttackRange(const FColor& DrawColor, FVector TraceStart, FVector TraceEnd, FVector Forward) { #if ENABLE_DRAW_DEBUG const float AttackRange = Stat->GetTotalStat().AttackRange; const float AttackRadius = Stat->GetAttackRadius(); FVector CapsuleOrigin = TraceStart + (TraceEnd - TraceStart) * 0.5f; float CapsuleHalfHeight = AttackRange * 0.5f; DrawDebugCapsule(GetWorld(), CapsuleOrigin, CapsuleHalfHeight, AttackRadius, FRotationMatrix::MakeFromZ(Forward).ToQuat(), DrawColor, false, 5.0f); #endif } bool AABCharacterPlayer::ServerRPCAttack_Validate(float AttackStartTime) { if (LastAttackStartTime == 0.0f) { return true; } return (AttackStartTime - LastAttackStartTime) > AttackTime; } void AABCharacterPlayer::ServerRPCAttack_Implementation(float AttackStartTime) { AB_LOG(LogABNetwork, Log, TEXT("%s"), TEXT("Begin")); bCanAttack = false; OnRep_CanAttack(); AttackTimeDifference = GetWorld()->GetTimeSeconds() - AttackStartTime; AB_LOG(LogABNetwork, Log, TEXT("LagTime : %f"), AttackTimeDifference); AttackTimeDifference = FMath::Clamp(AttackTimeDifference, 0.0f, AttackTime - 0.01f); FTimerHandle Handle; GetWorld()->GetTimerManager().SetTimer(Handle, FTimerDelegate::CreateLambda([&] { bCanAttack = true; OnRep_CanAttack(); } ), AttackTime - AttackTimeDifference, false, -1.0f); LastAttackStartTime = AttackStartTime; PlayAttackAnimation(); // 서버에서 공격 몽타주 재생 //MulticastRPCAttack(); for (APlayerController* PlayerController : TActorRange<APlayerController>(GetWorld())) { if (PlayerController && GetController() != PlayerController) { if(!PlayerController->IsLocalController()) { AABCharacterPlayer* OtherPlayer = Cast<AABCharacterPlayer>(PlayerController->GetPawn()); if (OtherPlayer) { OtherPlayer->ClientRPCPlayAnimation(this); } } } } } void AABCharacterPlayer::ClientRPCPlayAnimation_Implementation(AABCharacterPlayer* CharacterToPlay) { if (CharacterToPlay) { CharacterToPlay->PlayAttackAnimation(); } } void AABCharacterPlayer::MulticastRPCAttack_Implementation() { if (!IsLocallyControlled()) { PlayAttackAnimation(); } } bool AABCharacterPlayer::ServerRPCNotifyHit_Validate(const FHitResult& HitResult, float HitCheckTime) { return (HitCheckTime - LastAttackStartTime) > AcceptMinCheckTime; } void AABCharacterPlayer::ServerRPCNotifyHit_Implementation(const FHitResult& HitResult, float HitCheckTime) { AActor* HitActor = HitResult.GetActor(); if (::IsValid(HitActor)) { const FVector HitLocation = HitResult.Location; const FBox HitBox = HitActor->GetComponentsBoundingBox(); // 피격 받은 캐릭터를 전체로 감싸고 있는 BoundingBox 영역. const FVector ActorBoxCenter = (HitBox.Min + HitBox.Max) * 0.5f; // BoundingBox의 중점값 if (FVector::DistSquared(HitLocation, ActorBoxCenter) <= AcceptCheckDistance * AcceptCheckDistance) { AttackHitConfirm(HitActor); // 피격O } else { AB_LOG(LogABNetwork, Warning, TEXT("%s"), TEXT("HitTest Rejected!")); // 피격X } #if ENABLE_DRAW_DEBUG DrawDebugPoint(GetWorld(), ActorBoxCenter, 50.0f, FColor::Cyan, false, 5.0f); // 캐릭터 영역 중심 DrawDebugPoint(GetWorld(), HitLocation, 50.0f, FColor::Magenta, false, 5.0f); // 맞은 위치 #endif DrawDebugAttackRange(FColor::Green, HitResult.TraceStart, HitResult.TraceEnd, HitActor->GetActorForwardVector()); } } bool AABCharacterPlayer::ServerRPCNotifyMiss_Validate(FVector_NetQuantize TraceStart, FVector_NetQuantize TraceEnd, FVector_NetQuantizeNormal TraceDir, float HitCheckTime) { return (HitCheckTime - LastAttackStartTime) > AcceptMinCheckTime; } void AABCharacterPlayer::ServerRPCNotifyMiss_Implementation(FVector_NetQuantize TraceStart, FVector_NetQuantize TraceEnd, FVector_NetQuantizeNormal TraceDir, float HitCheckTime) { DrawDebugAttackRange(FColor::Red, TraceStart, TraceEnd, TraceDir); } void AABCharacterPlayer::OnRep_CanAttack() { if (!bCanAttack) { GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_None); } else { GetCharacterMovement()->SetMovementMode(EMovementMode::MOVE_Walking); } } void AABCharacterPlayer::SetupHUDWidget(UABHUDWidget* InHUDWidget) { // ... }
실행화면

'⭐ Unreal Engine > UE 개념정리 - Network' 카테고리의 다른 글
[UE Net] 캐릭터 공격 구현 (0) | 2024.04.10 |
---|---|
[UE Net] RPC (Remote Procedure Call) (0) | 2024.03.02 |
[UE Net] 액터 리플리케이션 로우레벨 플로우 Actor Replication - Low Level Flow (0) | 2024.02.12 |
[UE Net] 액터 리플리케이션 빈도와 연관성 Actor Replication Frequency & Relevancy + 언리얼 인사이트 Unreal Insight (0) | 2024.02.10 |
[UE Net] 액터 리플리케이션 Actor Replication (1) | 2024.02.10 |
댓글
이 글 공유하기
다른 글
-
[UE Net] 캐릭터 공격 구현
[UE Net] 캐릭터 공격 구현
2024.04.10네트웍 멀티플레이를 위한 캐릭터의 공격 구현과 체력 동기화 네트웍 멀티플레이 게임 제작을 위한 기본 구현 원칙의 학습 액터 컴포넌트를 리플리케이션하는 방법의 학습 인프런 이득우님의 '언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해' 강의를 참고하였습니다. 😎 [이득우의 언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해] 강의 들으러 가기! 목차 캐릭터 공격 구현 학습 목표 네트웍 멀티플레이를 위한 캐릭터의 공격 구현과 체력 동기화 캐릭터의 공격 플로우 (Standalone) 액터와 애님 인스턴스간에 수행되는 캐릭터의 공격 플로우 네트웍 멀티플레이의 구현을 위한 4원칙 1. 클라이언트의 명령은 Server RPC를 사용한다. Server RPC는 클라이언트에서 서버로 패킷… -
[UE Net] RPC (Remote Procedure Call)
[UE Net] RPC (Remote Procedure Call)
2024.03.02RPC의 기본 개념과 동작 원리를 학습하였다. 언리얼 C++에서 다양한 RPC를 사용하는 방법이 있는데 서버와 클라이언트 각각에서 실행되는지 여부를 잘 체크해야 한다. PROPERTY Replication과 RPC의 사용방법에는 차이가 있다. 두 방법의 차이점을 학습하여 상황에 따라 적절하게 활용하자. 인프런 이득우님의 '언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해' 강의를 참고하였습니다. 😎 [이득우의 언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해] 강의 들으러 가기! 목차 RPC (Remote Procedure Call) RPC의 기본 개념과 동작 원리를 이해하기언리얼 C++에서 다양한 RPC를 사용하는 방법을 학습하기PROPERTY Replicat… -
[UE Net] 액터 리플리케이션 로우레벨 플로우 Actor Replication - Low Level Flow
[UE Net] 액터 리플리케이션 로우레벨 플로우 Actor Replication - Low Level Flow
2024.02.12액터 리플리케이션의 내부 동작 원리를 이해하자. Actor Replication의 Priority의 개념과 동작 방식 이해하기 Actor의 휴면 상태 설정과 활용 방법의 학습 Server에서 Actor Replication을 처리하는 전반적인 플로우의 이해 인프런 이득우님의 '언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해' 강의를 참고하였습니다. 😎 [이득우의 언리얼 프로그래밍 Part3 - 네트웍 멀티플레이 프레임웍의 이해] 강의 들으러 가기! 목차 액터 리플리케이션의 우선권 Actor Replication Priority Actor Replication의 Priority의 개념과 동작 방식 이해하기 학습할 내용 우선권(Priority)이란? Client에 보내는 대역폭(NetBan… -
[UE Net] 액터 리플리케이션 빈도와 연관성 Actor Replication Frequency & Relevancy + 언리얼 인사이트 Unreal Insight
[UE Net] 액터 리플리케이션 빈도와 연관성 Actor Replication Frequency & Relevancy + 언리얼 인사이트 Unreal Insight
2024.02.10Network 통신 상태를 확인할 수 있는 언리얼 인사이트 도구의 활용 방법에 대해 알아보자. Actor Replication의 빈도 속성을 활용한 효율적인 Actor Replication의 구현해보자. Actor Replication에서 연관성의 개념과 용어 및 관련 설정의 학습하자. 거리 속성을 변경에 따른 연관성 동작의 구현해보자. 목차 Unreal Insight 액터 리플케이션의 연관성과 빈도에 대한 동작 방식의 확인 학습할 내용 - 연과성 & 빈도 언리얼 인사이트 Unreal Insight Network 통신 상태를 확인할 수 있는 언리얼 인사이트 도구의 활용 방법 언리얼 인사이트 Unreal Insights 언리얼 프로그램의 다양한 퍼포먼스를 체크할 수 있는 강력한 프로파일…
댓글을 사용할 수 없습니다.