1. 플레이어 쪽 함수 세팅
새로운 인풋 액션인 IA_ComboAttack을 추가한 후 Input Binding 진행
- Section을 사용해야 하기 때문에, SectionName에 사용할 변수를 추가
//헤더
FName ComboAttackSectionName;
FName DefaultComboAttackSectionName;
//cpp
void ADXPlayerCharacter::HandleComboAttackInput(const FInputActionValue& InValue)
{
if (!HasAuthority() && IsLocallyControlled())
{
PlayAnimMontage(ComboAttackMontage, 1.f, ComboAttackSectionName);
//ServerRPCComboAttack();
}
}
2. 애니메이션 노티파이 스테이트
애니메이션이 진행되는 동안 애니메이션 노티파이 스테이트를 통해 로직 수행
- 다음 section을 나타내는 변수 존재
- 해당 변수를 통해 다음 섹션으로 넘어갈 수 있도록 Player의 함수와 연동하기
//헤더
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Animation")
FName SectionName;
//cpp
void UDXANSMeleeCombo::NotifyBegin(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation, float TotalDuration)
{
Super::NotifyBegin(MeshComp, Animation, TotalDuration);
ADXPlayerCharacter* PlayerCharacter = Cast<ADXPlayerCharacter>(MeshComp->GetOwner());
if (IsValid(PlayerCharacter))
{
PlayerCharacter->SetNextComboAttack(SectionName);
}
}
void UDXANSMeleeCombo::NotifyEnd(USkeletalMeshComponent* MeshComp, UAnimSequenceBase* Animation)
{
Super::NotifyEnd(MeshComp, Animation);
ADXPlayerCharacter* PlayerCharacter = Cast<ADXPlayerCharacter>(MeshComp->GetOwner());
if (IsValid(PlayerCharacter))
{
PlayerCharacter->ResetComboAttack();
}
}
void ADXPlayerCharacter::SetNextComboAttack(FName InSectionName)
{
ComboAttackSectionName = InSectionName;
}
void ADXPlayerCharacter::ResetComboAttack()
{
ComboAttackSectionName = DefaultComboAttackSectionName;
}
3. 애니메이션 몽타주
애니메이션 몽타주에서 Section을 이용하여 콤보 공격을 구현할 것
- 먼저 애니메이션 몽타주를 만든 후 아래와 같이 사용하고자 하는 애니메이션을 넣기
- 각 애니메이션이 시작하는 곳에 section을 만든 다음 사용하고자 하는 이름으로 변경
- 전에 만들어둔 "애님 노티파이 스테이트"를 추가 (ANS는 다음 섹션으로 넘어가도 되는 부분부터 시작하도록)
각 애니메이션 노티파이 스테이트에 있어서 C++로 만들어 둔 변수에 아래와 같이 다음으로 재생할 section의 이름을 넣어준다.
- 아래 사진은 Combo1 섹션에 존재하는 ANS의 변수 세팅 모습
다음으로 각 section이 연결된 링크를 제거하여 모두 따로 분리해 준다.
분리하하면 아래와 같은 모습이 된다.
지금까지 실행 결과
- 아직 서버 관련 로직을 넣지 않았기 때문에 아래와 같이 자신의 화면에서만 보인다
- combo 공격은 잘 이루어지는 모습
4. 트러블 슈팅 (연속 공격 방지)
현재 문제는 연속된 input (연속 클릭) 시 첫 몽타주가 계속해서 플레이되는 문제가 존재한다.
문제 해결을 위해서 현재 몽타주가 재생중인지에 대한 변수를 추가해 줄 것이다.
또한 NotifyBegin 함수와 바인딩을 통해 해당 노티파이에서 실행할 함수를 만들어 주었다.
- bCanCombo 변수를 추가
- input 바인딩 된 함수에서 실행하면서 bCanCombo를 false로 변경
- OnMontageNotifyBegin이라는 함수를 OnPlayMontageNotifyBegin에 바인딩
- 위 애님 몽타주 사진에서 본 것처럼 "None" 몽타주 노티파이에서 바인딩 된 함수를 실행
- 바인딩 된 함수에서는 다시 bCanCombo를 true로 만들어주기
void ADXPlayerCharacter::BeginPlay()
{
Super::BeginPlay();
...
UDXAnimInstanceBase* Anim = Cast<UDXAnimInstanceBase>(GetMesh()->GetAnimInstance());
if (IsValid(Anim))
{
Anim->OnPlayMontageNotifyBegin.AddDynamic(this, &ThisClass::OnMontageNotifyBegin);
}
}
void ADXPlayerCharacter::HandleComboAttackInput(const FInputActionValue& InValue)
{
if (!HasAuthority() && IsLocallyControlled())
{
if (bCanCombo)
{
PlayAnimMontage(ComboAttackMontage, 1.f, ComboAttackSectionName);
bCanCombo = false;
}
}
}
void ADXPlayerCharacter::OnMontageNotifyBegin(FName NotifyName, const FBranchingPointNotifyPayload& PayLoad)
{
if (NotifyName == "None")
{
bCanCombo = true;
}
}
결과 모습
5. 서버 로직 추가 (몽타주 재생)
우선 서버에서 해당 애니메이션을 볼 수 있도록 처리를 할 것이다.
콤보 공격 몽타주를 출력해주는 코드를 함수로 따로 빼주고
void ADXPlayerCharacter::PlayComboAttackMontage()
{
PlayAnimMontage(ComboAttackMontage, 1.f, ComboAttackSectionName);
}
아래와 같이 인풋 바인딩 시 실행되는 함수를 수정해 주었다.
- 몽타주 함수 실행
- ServerRPC 함수 추가
void ADXPlayerCharacter::HandleComboAttackInput(const FInputActionValue& InValue)
{
if (!HasAuthority() && IsLocallyControlled())
{
if (bCanCombo)
{
PlayComboAttackMontage();
bCanCombo = false;
ServerRPCComboAttack();
}
}
}
ServerRPC 함수에서는 아래와 같이 서버와 자기 자신을 제외한 플레이어에게 Multicast 함수를 통해 몽타주를 재생하도록 한다.
//헤더
UFUNCTION(Server, Reliable)
void ServerRPCComboAttack();
UFUNCTION(NetMulticast, Unreliable)
void MulticastRPCComboAttack();
//cpp
void ADXPlayerCharacter::ServerRPCComboAttack_Implementation()
{
MulticastRPCComboAttack();
}
void ADXPlayerCharacter::MulticastRPCComboAttack_Implementation()
{
//except server and owner player
if (!HasAuthority() && !IsLocallyControlled())
{
PlayComboAttackMontage();
}
}
결과
- 몽타주가 다른 클라이언트에게도 보이는 것을 확인할 수 있다.
6. 서버 로직 추가 (데미지 처리)
생각할 점) 데미지 처리 로직은 중요한 로직이기 때문에 서버에서 처리를 해야한다.
데미지를 입히는 함수를 아래와 같이 서버에서 돌아가는 로직으로 아래와 같이 작성해 볼 수 있다.
void ADXPlayerCharacter::CheckMeleeAttackHit()
{
//server
if (HasAuthority())
{
TArray<FHitResult> OutHitResults;
TSet<ACharacter*> DamagedCharacters;
FCollisionQueryParams Params(NAME_None, false, this);
const float MeleeAttackRange = 50.f;
const float MeleeAttackRadius = 50.f;
const float MeleeAttackDamage = 10.f;
const FVector Forward = GetActorForwardVector();
const FVector Start = GetActorLocation() + Forward * GetCapsuleComponent()->GetScaledCapsuleRadius();
const FVector End = Start + GetActorForwardVector() * MeleeAttackRange;
bool bIsHitDetected = GetWorld()->SweepMultiByChannel(OutHitResults, Start, End, FQuat::Identity, ECC_Camera, FCollisionShape::MakeSphere(MeleeAttackRadius), Params);
if (bIsHitDetected)
{
for (auto const& OutHitResult : OutHitResults)
{
ACharacter* DamagedCharacter = Cast<ACharacter>(OutHitResult.GetActor());
if (IsValid(DamagedCharacter))
{
DamagedCharacters.Add(DamagedCharacter);
}
}
FDamageEvent DamageEvent;
for (auto const& DamagedCharacter : DamagedCharacters)
{
DamagedCharacter->TakeDamage(MeleeAttackDamage, DamageEvent, GetController(), this);
}
}
FColor DrawColor = bIsHitDetected ? FColor::Green : FColor::Red;
MulticastDrawDebugMeleeAttack(DrawColor, Start, End, Forward);
}
}
참고) TakeDamage 함수는 기존의 TakeDamage 함수를 override한 함수이다.
virtual float TakeDamage(float DamageAmount, struct FDamageEvent const& DamageEvent, class AController* EventInstigator, AActor* DamageCauser) override;
위에서 만든 로직은 애니메이션 몽타주에서 재생할 수 있도록 애님 인스턴스 클래스에 Notify 함수를 만들어서 실행시켰다.
//헤더
UFUNCTION()
void AnimNotify_CheckMeleeAttackHit();
//cpp
void UDXAnimInstanceBase::AnimNotify_CheckMeleeAttackHit()
{
ADXPlayerCharacter* OwnerPlayerCharacter = Cast<ADXPlayerCharacter>(OwnerCharacter);
if (IsValid(OwnerPlayerCharacter))
{
OwnerPlayerCharacter->CheckMeleeAttackHit();
}
}
- 새로 만든 노티파이라면,
- 노티파이 칸 우클릭 -> 노티파이 추가 -> 새 노티파이 -> 이름 추가(위에서 만든 함수에서 언더바 이후의 이름 사용)
- 이전에 만들었던 노티파이라면
- 아래와 같이 선택
- 추가) 몽타주 틱 타입을 Branching Point로 수정
- 완성된 모습
7. 트러블 슈팅 (데미지 처리)
그러나 위의 설정만 진행한 다음 실행하면, 공격 처리가 안된다. (아래 영상 참고)
원인을 찾아보기 위해 현재 로직을 살펴보면
- 로컬 플레이어만 인풋 바인딩 함수 실행
- 몽타주 실행
- 서버 RPC 실행
- 로컬 플레이어의 몽타주 실행의 경우
- 서버에서 실행되는 로직이 없으므로 CheckMeleeAttackHit 함수가 실행될 수 없다.
- 서버 RPC를 실행한 경우
- 멀티캐스트를 하지만, 멀티캐스트 함수에서 !HasAuthority() 일때 실행되기 때문에
- 마찬가지로 서버의 로직이 없다.
해결하기 위해서는
- CheckMeleeAttackHit 함수를 ServerRPC 함수로 만들거나
- 멀티캐스트를 서버에서도 진행하게 해주는 방법이 있다.
이번에는 멀티캐스트를 서버에서 진행할 수 있도록 하는 방법을 사용해서 해결해 볼 것이다.
- 아래와 같이 멀티캐스트 함수를 수정
void ADXPlayerCharacter::MulticastRPCComboAttack_Implementation()
{
if (!IsLocallyControlled())
{
PlayComboAttackMontage();
}
}
결과 모습
'Unreal Engine' 카테고리의 다른 글
UE5 Issues : 데디케이트 서버 UI에 관해 (0) | 2025.06.12 |
---|---|
Unreal Engine - 데디케이티드 서버 11 (콤보 공격 최적화) (0) | 2025.06.02 |
UE5 Issues - Dash Skill (데디케이티드 서버) (0) | 2025.05.27 |
Unreal Engine - 데디케이티드 서버 9 (게임 종료) (0) | 2025.05.04 |
Unreal Engine - 데디케이티드 서버 8 (게임 흐름) (0) | 2025.05.02 |